Completed
Push — master ( 9bb37d...ddb6c0 )
by Alaa
02:08
created

testLogsInfoOnFallbackToMaster()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 9.44
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Wikibase\TermStore\MediaWiki\Tests\Integration\PackagePrivate;
4
5
use PHPUnit\Framework\TestCase;
6
use Psr\Log\AbstractLogger;
7
use Psr\Log\LoggerInterface;
8
use Wikibase\TermStore\MediaWiki\PackagePrivate\DatabaseTermIdsResolver;
9
use Wikibase\TermStore\MediaWiki\PackagePrivate\StaticTypeIdsStore;
10
use Wikibase\TermStore\MediaWiki\PackagePrivate\TypeIdsResolver;
11
use Wikibase\TermStore\MediaWiki\TermStoreSchemaUpdater;
12
use Wikibase\TermStore\MediaWiki\Tests\Util\FakeLoadBalancer;
13
use Wikimedia\Rdbms\Database;
14
use Wikimedia\Rdbms\DatabaseSqlite;
15
use Wikimedia\Rdbms\IDatabase;
16
use Wikimedia\Rdbms\ILoadBalancer;
17
18
/**
19
 * @covers \Wikibase\TermStore\MediaWiki\PackagePrivate\DatabaseTermIdsResolver
20
 */
21
class DatabaseTermIdsResolverTest extends TestCase {
22
23
	const TYPE_LABEL = 1;
24
	const TYPE_DESCRIPTION = 2;
25
	const TYPE_ALIAS = 3;
26
27
	/** @var TypeIdsResolver */
28
	private $typeIdsResolver;
29
30
	/** @var IDatabase */
31
	private $db;
32
33
	public function setUp() {
34
		$this->typeIdsResolver = new StaticTypeIdsStore( [
35
			'label' => self::TYPE_LABEL,
36
			'description' => self::TYPE_DESCRIPTION,
37
			'alias' => self::TYPE_ALIAS,
38
		] );
39
		$this->db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
40
		$this->db->sourceFile( TermStoreSchemaUpdater::getSqlFileAbsolutePath() );
41
	}
42
43
	public function testCanResolveEverything() {
44
		$this->db->insert( 'wbt_text',
45
			[ 'wbx_text' => 'text' ] );
46
		$text1Id = $this->db->insertId();
47
		$this->db->insert( 'wbt_text',
48
			[ 'wbx_text' => 'Text' ] );
49
		$text2Id = $this->db->insertId();
50
		$this->db->insert( 'wbt_text_in_lang',
51
			[ 'wbxl_language' => 'en', 'wbxl_text_id' => $text1Id ] );
52
		$textInLang1Id = $this->db->insertId();
53
		$this->db->insert( 'wbt_text_in_lang',
54
			[ 'wbxl_language' => 'de', 'wbxl_text_id' => $text2Id ] );
55
		$textInLang2Id = $this->db->insertId();
56
		$this->db->insert( 'wbt_term_in_lang',
57
			[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLang1Id ] );
58
		$termInLang1Id = $this->db->insertId();
59
		$this->db->insert( 'wbt_term_in_lang',
60
			[ 'wbtl_type_id' => self::TYPE_DESCRIPTION, 'wbtl_text_in_lang_id' => $textInLang1Id ] );
61
		$termInLang2Id = $this->db->insertId();
62
		$this->db->insert( 'wbt_term_in_lang',
63
			[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLang2Id ] );
64
		$termInLang3Id = $this->db->insertId();
65
66
		$resolver = new DatabaseTermIdsResolver(
67
			$this->typeIdsResolver,
68
			new FakeLoadBalancer( [
69
				'dbr' => $this->db,
70
			] )
71
		);
72
		$terms = $resolver->resolveTermIds( [ $termInLang1Id, $termInLang2Id, $termInLang3Id ] );
73
74
		$this->assertSame( [
75
			'label' => [
76
				'en' => [ 'text' ],
77
				'de' => [ 'Text' ],
78
			],
79
			'description' => [
80
				'en' => [ 'text' ],
81
			],
82
		], $terms );
83
	}
84
85
	public function testReadsEverythingFromReplicaIfPossible() {
86
		$this->db->insert( 'wbt_text',
87
			[ 'wbx_text' => 'text' ] );
88
		$text1Id = $this->db->insertId();
89
		$this->db->insert( 'wbt_text',
90
			[ 'wbx_text' => 'Text' ] );
91
		$text2Id = $this->db->insertId();
92
		$this->db->insert( 'wbt_text_in_lang',
93
			[ 'wbxl_language' => 'en', 'wbxl_text_id' => $text1Id ] );
94
		$textInLang1Id = $this->db->insertId();
95
		$this->db->insert( 'wbt_text_in_lang',
96
			[ 'wbxl_language' => 'de', 'wbxl_text_id' => $text2Id ] );
97
		$textInLang2Id = $this->db->insertId();
98
		$this->db->insert( 'wbt_term_in_lang',
99
			[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLang1Id ] );
100
		$termInLang1Id = $this->db->insertId();
101
		$this->db->insert( 'wbt_term_in_lang',
102
			[ 'wbtl_type_id' => self::TYPE_DESCRIPTION, 'wbtl_text_in_lang_id' => $textInLang1Id ] );
103
		$termInLang2Id = $this->db->insertId();
104
		$this->db->insert( 'wbt_term_in_lang',
105
			[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLang2Id ] );
106
		$termInLang3Id = $this->db->insertId();
107
108
		$dbw = $this->createMock( Database::class );
109
		$dbw->expects( $this->never() )->method( 'query' );
110
111
		$resolver = new DatabaseTermIdsResolver(
112
			$this->typeIdsResolver,
113
			new FakeLoadBalancer( [
114
				'dbr' => $this->db,
115
				'dbw' => $dbw,
116
			] )
117
		);
118
		$resolver->resolveTermIds( [ $termInLang1Id, $termInLang2Id, $termInLang3Id ] );
119
	}
120
121
	public function testFallsBackToMasterIfNecessaryAndAllowed() {
122
		$dbr = $this->db;
123
		$dbw = DatabaseSqlite::newStandaloneInstance( ':memory:' );
124
		$dbw->sourceFile( TermStoreSchemaUpdater::getSqlFileAbsolutePath() );
125
		// both master and replica have most of the data
126
		foreach ( [ $dbr, $dbw ] as $db ) {
127
			// note: we assume that both DBs get the same insert IDs
128
			$db->insert( 'wbt_text',
129
				[ 'wbx_text' => 'text' ] );
130
			$text1Id = $db->insertId();
131
			$db->insert( 'wbt_text',
132
				[ 'wbx_text' => 'Text' ] );
133
			$text2Id = $db->insertId();
134
			$db->insert( 'wbt_text_in_lang',
135
				[ 'wbxl_language' => 'en', 'wbxl_text_id' => $text1Id ] );
136
			$textInLang1Id = $db->insertId();
137
			$db->insert( 'wbt_text_in_lang',
138
				[ 'wbxl_language' => 'de', 'wbxl_text_id' => $text2Id ] );
139
			$textInLang2Id = $db->insertId();
140
			$db->insert( 'wbt_term_in_lang',
141
				[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLang1Id ] );
142
			$termInLang1Id = $db->insertId();
143
			$db->insert( 'wbt_term_in_lang',
144
				[ 'wbtl_type_id' => self::TYPE_DESCRIPTION, 'wbtl_text_in_lang_id' => $textInLang1Id ] );
145
			$termInLang2Id = $db->insertId();
146
		}
147
		// only master has the last term_in_lang row
148
		$db->insert( 'wbt_term_in_lang',
0 ignored issues
show
Bug introduced by
The variable $db seems to be defined by a foreach iteration on line 126. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
149
			[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLang2Id ] );
0 ignored issues
show
Bug introduced by
The variable $textInLang2Id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
150
		$termInLang3Id = $db->insertId();
151
152
		$resolver = new DatabaseTermIdsResolver(
153
			$this->typeIdsResolver,
154
			new FakeLoadBalancer( [
155
				'dbr' => $dbr,
156
				'dbw' => $dbw,
157
			] ),
158
			true
159
		);
160
		$terms = $resolver->resolveTermIds( [ $termInLang1Id, $termInLang2Id, $termInLang3Id ] );
0 ignored issues
show
Bug introduced by
The variable $termInLang1Id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $termInLang2Id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
161
162
		$this->assertSame( [
163
			'label' => [
164
				'en' => [ 'text' ],
165
				'de' => [ 'Text' ],
166
			],
167
			'description' => [
168
				'en' => [ 'text' ],
169
			],
170
		], $terms );
171
	}
172
173
	public function testDoesNotFallBackToMasterIfNotAllowed() {
174
		$dbr = $this->db;
175
		$dbw = DatabaseSqlite::newStandaloneInstance( ':memory:' );
176
		$dbw->sourceFile( TermStoreSchemaUpdater::getSqlFileAbsolutePath() );
177
		// both master and replica have most of the data
178
		foreach ( [ $dbr, $dbw ] as $db ) {
179
			// note: we assume that both DBs get the same insert IDs
180
			$db->insert( 'wbt_text',
181
				[ 'wbx_text' => 'text' ] );
182
			$text1Id = $db->insertId();
183
			$db->insert( 'wbt_text',
184
				[ 'wbx_text' => 'Text' ] );
185
			$text2Id = $db->insertId();
186
			$db->insert( 'wbt_text_in_lang',
187
				[ 'wbxl_language' => 'en', 'wbxl_text_id' => $text1Id ] );
188
			$textInLang1Id = $db->insertId();
189
			$db->insert( 'wbt_text_in_lang',
190
				[ 'wbxl_language' => 'de', 'wbxl_text_id' => $text2Id ] );
191
			$textInLang2Id = $db->insertId();
192
			$db->insert( 'wbt_term_in_lang',
193
				[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLang1Id ] );
194
			$termInLang1Id = $db->insertId();
195
			$db->insert( 'wbt_term_in_lang',
196
				[ 'wbtl_type_id' => self::TYPE_DESCRIPTION, 'wbtl_text_in_lang_id' => $textInLang1Id ] );
197
			$termInLang2Id = $db->insertId();
198
		}
199
		// only master has the last term_in_lang row
200
		$db->insert( 'wbt_term_in_lang',
0 ignored issues
show
Bug introduced by
The variable $db seems to be defined by a foreach iteration on line 178. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
201
			[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLang2Id ] );
0 ignored issues
show
Bug introduced by
The variable $textInLang2Id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
202
		$termInLang3Id = $db->insertId();
203
204
		$resolver = new DatabaseTermIdsResolver(
205
			$this->typeIdsResolver,
206
			new FakeLoadBalancer( [
207
				'dbr' => $dbr,
208
				'dbw' => $dbw,
209
			] ),
210
			false
211
		);
212
		$terms = $resolver->resolveTermIds( [ $termInLang1Id, $termInLang2Id, $termInLang3Id ] );
0 ignored issues
show
Bug introduced by
The variable $termInLang1Id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $termInLang2Id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
213
214
		$this->assertSame( [
215
			'label' => [
216
				'en' => [ 'text' ],
217
				// 'de' => [ 'Text' ], // this is the row missing from the replica
218
			],
219
			'description' => [
220
				'en' => [ 'text' ],
221
			],
222
		], $terms );
223
	}
224
225
	public function testLogsInfoOnFallbackToMaster() {
226
		$dbr = $this->db;
227
		$dbw = DatabaseSqlite::newStandaloneInstance( ':memory:' );
228
		$dbw->sourceFile( TermStoreSchemaUpdater::getSqlFileAbsolutePath() );
229
		// only master has any data
230
		$dbw->insert( 'wbt_text',
231
			[ 'wbx_text' => 'text' ] );
232
		$textId = $dbw->insertId();
233
		$dbw->insert( 'wbt_text_in_lang',
234
			[ 'wbxl_language' => 'en', 'wbxl_text_id' => $textId ] );
235
		$textInLangId = $dbw->insertId();
236
		$dbw->insert( 'wbt_term_in_lang',
237
			[ 'wbtl_type_id' => self::TYPE_LABEL, 'wbtl_text_in_lang_id' => $textInLangId ] );
238
		$termInLangId = $dbw->insertId();
239
240
		$logger = $this->createMock( AbstractLogger::class );
241
		$logger->expects( $this->atLeastOnce() )
242
			->method( 'info' );
243
244
		$resolver = new DatabaseTermIdsResolver(
245
			$this->typeIdsResolver,
246
			new FakeLoadBalancer( [
247
				'dbr' => $dbr,
248
				'dbw' => $dbw,
249
			] ),
250
			true,
251
			$logger
0 ignored issues
show
Documentation introduced by
$logger is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a null|object<Psr\Log\LoggerInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
252
		);
253
		$resolver->resolveTermIds( [ $termInLangId ] );
254
	}
255
256
}
257