Separate MediaWiki unit and integration tests
[lhc/web/wiklou.git] / tests / phpunit / unit / includes / resourceloader / MessageBlobStoreTest.php
1 <?php
2
3 use Wikimedia\Rdbms\Database;
4 use Wikimedia\Rdbms\LoadBalancer;
5 use Wikimedia\TestingAccessWrapper;
6
7 /**
8 * @group ResourceLoader
9 * @covers MessageBlobStore
10 */
11 class MessageBlobStoreTest extends MediaWikiUnitTestCase {
12
13 use MediaWikiCoversValidator;
14 use PHPUnit4And6Compat;
15
16 protected function setUp() {
17 parent::setUp();
18 // MediaWiki's test wrapper sets $wgMainWANCache to CACHE_NONE.
19 // Use HashBagOStuff here so that we can observe caching.
20 $this->wanCache = new WANObjectCache( [
21 'cache' => new HashBagOStuff()
22 ] );
23
24 $this->clock = 1301655600.000;
25 $this->wanCache->setMockTime( $this->clock );
26
27 $lbMock = $this->createMock( LoadBalancer::class );
28 $dbMock = $this->getMockBuilder( Database::class )
29 ->disableOriginalConstructor()
30 ->getMockForAbstractClass();
31
32 $lbMock->expects( $this->any() )
33 ->method( 'getConnection' )
34 ->willReturn( $dbMock );
35
36 $lbMockFactory = function () use ( $lbMock ): LoadBalancer {
37 return $lbMock;
38 };
39
40 $this->overrideMwServices( [ 'DBLoadBalancer' => $lbMockFactory ] );
41 }
42
43 public function testBlobCreation() {
44 $module = $this->makeModule( [ 'mainpage' ] );
45 $rl = new EmptyResourceLoader();
46 $rl->register( $module->getName(), $module );
47
48 $blobStore = $this->makeBlobStore( null, $rl );
49 $blob = $blobStore->getBlob( $module, 'en' );
50
51 $this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' );
52 }
53
54 public function testBlobCreation_empty() {
55 $module = $this->makeModule( [] );
56 $rl = new EmptyResourceLoader();
57 $rl->register( $module->getName(), $module );
58
59 $blobStore = $this->makeBlobStore( null, $rl );
60 $blob = $blobStore->getBlob( $module, 'en' );
61
62 $this->assertEquals( '{}', $blob, 'Generated blob' );
63 }
64
65 public function testBlobCreation_unknownMessage() {
66 $module = $this->makeModule( [ 'i-dont-exist', 'mainpage', 'i-dont-exist2' ] );
67 $rl = new EmptyResourceLoader();
68 $rl->register( $module->getName(), $module );
69 $blobStore = $this->makeBlobStore( null, $rl );
70
71 // Generating a blob should continue without errors,
72 // with keys of unknown messages excluded from the blob.
73 $blob = $blobStore->getBlob( $module, 'en' );
74 $this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' );
75 }
76
77 public function testMessageCachingAndPurging() {
78 $module = $this->makeModule( [ 'example' ] );
79 $rl = new EmptyResourceLoader();
80 $rl->register( $module->getName(), $module );
81 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
82
83 // Advance this new WANObjectCache instance to a normal state,
84 // by doing one "get" and letting its hold off period expire.
85 // Without this, the first real "get" would lazy-initialise the
86 // checkKey and thus reject the first "set".
87 $blobStore->getBlob( $module, 'en' );
88 $this->clock += 20;
89
90 // Arrange version 1 of a message
91 $blobStore->expects( $this->once() )
92 ->method( 'fetchMessage' )
93 ->will( $this->returnValue( 'First version' ) );
94
95 // Assert
96 $blob = $blobStore->getBlob( $module, 'en' );
97 $this->assertEquals( '{"example":"First version"}', $blob, 'Blob for v1' );
98
99 // Arrange version 2
100 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
101 $blobStore->expects( $this->once() )
102 ->method( 'fetchMessage' )
103 ->will( $this->returnValue( 'Second version' ) );
104 $this->clock += 20;
105
106 // Assert
107 // We do not validate whether a cached message is up-to-date.
108 // Instead, changes to messages will send us a purge.
109 // When cache is not purged or expired, it must be used.
110 $blob = $blobStore->getBlob( $module, 'en' );
111 $this->assertEquals( '{"example":"First version"}', $blob, 'Reuse cached v1 blob' );
112
113 // Purge cache
114 $blobStore->updateMessage( 'example' );
115 $this->clock += 20;
116
117 // Assert
118 $blob = $blobStore->getBlob( $module, 'en' );
119 $this->assertEquals( '{"example":"Second version"}', $blob, 'Updated blob for v2' );
120 }
121
122 public function testPurgeEverything() {
123 $module = $this->makeModule( [ 'example' ] );
124 $rl = new EmptyResourceLoader();
125 $rl->register( $module->getName(), $module );
126 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
127 // Advance this new WANObjectCache instance to a normal state.
128 $blobStore->getBlob( $module, 'en' );
129 $this->clock += 20;
130
131 // Arrange version 1 and 2
132 $blobStore->expects( $this->exactly( 2 ) )
133 ->method( 'fetchMessage' )
134 ->will( $this->onConsecutiveCalls( 'First', 'Second' ) );
135
136 // Assert
137 $blob = $blobStore->getBlob( $module, 'en' );
138 $this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1' );
139
140 $this->clock += 20;
141
142 // Assert
143 $blob = $blobStore->getBlob( $module, 'en' );
144 $this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1 again' );
145
146 // Purge everything
147 $blobStore->clear();
148 $this->clock += 20;
149
150 // Assert
151 $blob = $blobStore->getBlob( $module, 'en' );
152 $this->assertEquals( '{"example":"Second"}', $blob, 'Blob for v2' );
153 }
154
155 public function testValidateAgainstModuleRegistry() {
156 // Arrange version 1 of a module
157 $module = $this->makeModule( [ 'foo' ] );
158 $rl = new EmptyResourceLoader();
159 $rl->register( $module->getName(), $module );
160 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
161 $blobStore->expects( $this->once() )
162 ->method( 'fetchMessage' )
163 ->will( $this->returnValueMap( [
164 // message key, language code, message value
165 [ 'foo', 'en', 'Hello' ],
166 ] ) );
167
168 // Assert
169 $blob = $blobStore->getBlob( $module, 'en' );
170 $this->assertEquals( '{"foo":"Hello"}', $blob, 'Blob for v1' );
171
172 // Arrange version 2 of module
173 // While message values may be out of date, the set of messages returned
174 // must always match the set of message keys required by the module.
175 // We do not receive purges for this because no messages were changed.
176 $module = $this->makeModule( [ 'foo', 'bar' ] );
177 $rl = new EmptyResourceLoader();
178 $rl->register( $module->getName(), $module );
179 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
180 $blobStore->expects( $this->exactly( 2 ) )
181 ->method( 'fetchMessage' )
182 ->will( $this->returnValueMap( [
183 // message key, language code, message value
184 [ 'foo', 'en', 'Hello' ],
185 [ 'bar', 'en', 'World' ],
186 ] ) );
187
188 // Assert
189 $blob = $blobStore->getBlob( $module, 'en' );
190 $this->assertEquals( '{"foo":"Hello","bar":"World"}', $blob, 'Blob for v2' );
191 }
192
193 public function testSetLoggedIsVoid() {
194 $blobStore = $this->makeBlobStore();
195 $this->assertSame( null, $blobStore->setLogger( new Psr\Log\NullLogger() ) );
196 }
197
198 private function makeBlobStore( $methods = null, $rl = null ) {
199 $blobStore = $this->getMockBuilder( MessageBlobStore::class )
200 ->setConstructorArgs( [ $rl ?? $this->createMock( ResourceLoader::class ) ] )
201 ->setMethods( $methods )
202 ->getMock();
203
204 $access = TestingAccessWrapper::newFromObject( $blobStore );
205 $access->wanCache = $this->wanCache;
206 return $blobStore;
207 }
208
209 private function makeModule( array $messages ) {
210 $module = new ResourceLoaderTestModule( [ 'messages' => $messages ] );
211 $module->setName( 'test.blobstore' );
212 return $module;
213 }
214 }