3 use Wikimedia\Rdbms\Database
;
4 use Wikimedia\Rdbms\LoadBalancer
;
5 use Wikimedia\TestingAccessWrapper
;
8 * @group ResourceLoader
9 * @covers MessageBlobStore
11 class MessageBlobStoreTest
extends MediaWikiUnitTestCase
{
13 use MediaWikiCoversValidator
;
14 use PHPUnit4And6Compat
;
16 protected function 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()
24 $this->clock
= 1301655600.000;
25 $this->wanCache
->setMockTime( $this->clock
);
27 $lbMock = $this->createMock( LoadBalancer
::class );
28 $dbMock = $this->getMockBuilder( Database
::class )
29 ->disableOriginalConstructor()
30 ->getMockForAbstractClass();
32 $lbMock->expects( $this->any() )
33 ->method( 'getConnection' )
34 ->willReturn( $dbMock );
36 $lbMockFactory = function () use ( $lbMock ): LoadBalancer
{
40 $this->overrideMwServices( [ 'DBLoadBalancer' => $lbMockFactory ] );
43 public function testBlobCreation() {
44 $module = $this->makeModule( [ 'mainpage' ] );
45 $rl = new EmptyResourceLoader();
46 $rl->register( $module->getName(), $module );
48 $blobStore = $this->makeBlobStore( null, $rl );
49 $blob = $blobStore->getBlob( $module, 'en' );
51 $this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' );
54 public function testBlobCreation_empty() {
55 $module = $this->makeModule( [] );
56 $rl = new EmptyResourceLoader();
57 $rl->register( $module->getName(), $module );
59 $blobStore = $this->makeBlobStore( null, $rl );
60 $blob = $blobStore->getBlob( $module, 'en' );
62 $this->assertEquals( '{}', $blob, 'Generated blob' );
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 );
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' );
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 );
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' );
90 // Arrange version 1 of a message
91 $blobStore->expects( $this->once() )
92 ->method( 'fetchMessage' )
93 ->will( $this->returnValue( 'First version' ) );
96 $blob = $blobStore->getBlob( $module, 'en' );
97 $this->assertEquals( '{"example":"First version"}', $blob, 'Blob for v1' );
100 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
101 $blobStore->expects( $this->once() )
102 ->method( 'fetchMessage' )
103 ->will( $this->returnValue( 'Second version' ) );
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' );
114 $blobStore->updateMessage( 'example' );
118 $blob = $blobStore->getBlob( $module, 'en' );
119 $this->assertEquals( '{"example":"Second version"}', $blob, 'Updated blob for v2' );
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' );
131 // Arrange version 1 and 2
132 $blobStore->expects( $this->exactly( 2 ) )
133 ->method( 'fetchMessage' )
134 ->will( $this->onConsecutiveCalls( 'First', 'Second' ) );
137 $blob = $blobStore->getBlob( $module, 'en' );
138 $this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1' );
143 $blob = $blobStore->getBlob( $module, 'en' );
144 $this->assertEquals( '{"example":"First"}', $blob, 'Blob for v1 again' );
151 $blob = $blobStore->getBlob( $module, 'en' );
152 $this->assertEquals( '{"example":"Second"}', $blob, 'Blob for v2' );
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' ],
169 $blob = $blobStore->getBlob( $module, 'en' );
170 $this->assertEquals( '{"foo":"Hello"}', $blob, 'Blob for v1' );
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' ],
189 $blob = $blobStore->getBlob( $module, 'en' );
190 $this->assertEquals( '{"foo":"Hello","bar":"World"}', $blob, 'Blob for v2' );
193 public function testSetLoggedIsVoid() {
194 $blobStore = $this->makeBlobStore();
195 $this->assertSame( null, $blobStore->setLogger( new Psr\Log\
NullLogger() ) );
198 private function makeBlobStore( $methods = null, $rl = null ) {
199 $blobStore = $this->getMockBuilder( MessageBlobStore
::class )
200 ->setConstructorArgs( [ $rl ??
$this->createMock( ResourceLoader
::class ) ] )
201 ->setMethods( $methods )
204 $access = TestingAccessWrapper
::newFromObject( $blobStore );
205 $access->wanCache
= $this->wanCache
;
209 private function makeModule( array $messages ) {
210 $module = new ResourceLoaderTestModule( [ 'messages' => $messages ] );
211 $module->setName( 'test.blobstore' );