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