1<?php 2 3use Wikimedia\Rdbms\LBFactory; 4 5/** 6 * @covers ExternalStoreFactory 7 * @covers ExternalStoreAccess 8 */ 9class ExternalStoreFactoryTest extends MediaWikiIntegrationTestCase { 10 11 public function testExternalStoreFactory_noStores1() { 12 $factory = new ExternalStoreFactory( [], [], 'test-id' ); 13 $this->expectException( ExternalStoreException::class ); 14 $factory->getStore( 'ForTesting' ); 15 } 16 17 public function testExternalStoreFactory_noStores2() { 18 $factory = new ExternalStoreFactory( [], [], 'test-id' ); 19 $this->expectException( ExternalStoreException::class ); 20 $factory->getStore( 'foo' ); 21 } 22 23 public function provideStoreNames() { 24 yield 'Same case as construction' => [ 'ForTesting' ]; 25 yield 'All lower case' => [ 'fortesting' ]; 26 yield 'All upper case' => [ 'FORTESTING' ]; 27 yield 'Mix of cases' => [ 'FOrTEsTInG' ]; 28 } 29 30 /** 31 * @dataProvider provideStoreNames 32 */ 33 public function testExternalStoreFactory_someStore_protoMatch( $proto ) { 34 $factory = new ExternalStoreFactory( [ 'ForTesting' ], [], 'test-id' ); 35 $store = $factory->getStore( $proto ); 36 $this->assertInstanceOf( ExternalStoreForTesting::class, $store ); 37 } 38 39 /** 40 * @dataProvider provideStoreNames 41 */ 42 public function testExternalStoreFactory_someStore_noProtoMatch( $proto ) { 43 $factory = new ExternalStoreFactory( [ 'SomeOtherClassName' ], [], 'test-id' ); 44 $this->expectException( ExternalStoreException::class ); 45 $factory->getStore( $proto ); 46 } 47 48 /** 49 * @covers ExternalStoreFactory::getProtocols 50 * @covers ExternalStoreFactory::getWriteBaseUrls 51 * @covers ExternalStoreFactory::getStore 52 */ 53 public function testStoreFactoryBasic() { 54 $active = [ 'memory', 'mwstore' ]; 55 $defaults = [ 'memory://cluster1', 'memory://cluster2', 'mwstore://memstore1' ]; 56 $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' ); 57 $this->setMwGlobals( 'wgFileBackends', [ 58 [ 59 'name' => 'memstore1', 60 'class' => 'MemoryFileBackend', 61 'domain' => 'its-all-in-your-head', 62 'readOnly' => 'reason is a lie', 63 'lockManager' => 'nullLockManager' 64 ] 65 ] ); 66 67 $this->assertEquals( $active, $esFactory->getProtocols() ); 68 $this->assertEquals( $defaults, $esFactory->getWriteBaseUrls() ); 69 70 /** @var ExternalStoreMemory $store */ 71 $store = $esFactory->getStore( 'memory' ); 72 $this->assertInstanceOf( ExternalStoreMemory::class, $store ); 73 $this->assertFalse( $store->isReadOnly( 'cluster1' ), "Location is writable" ); 74 $this->assertFalse( $store->isReadOnly( 'cluster2' ), "Location is writable" ); 75 76 $mwStore = $esFactory->getStore( 'mwstore' ); 77 $this->assertTrue( $mwStore->isReadOnly( 'memstore1' ), "Location is read-only" ); 78 79 $lb = $this->getMockBuilder( \Wikimedia\Rdbms\LoadBalancer::class ) 80 ->disableOriginalConstructor()->getMock(); 81 $lb->method( 'getReadOnlyReason' )->willReturn( 'Locked' ); 82 $lbFactory = $this->getMockBuilder( LBFactory::class ) 83 ->disableOriginalConstructor()->getMock(); 84 $lbFactory->method( 'getExternalLB' )->willReturn( $lb ); 85 86 $this->setService( 'DBLoadBalancerFactory', $lbFactory ); 87 88 $active = [ 'db', 'mwstore' ]; 89 $defaults = [ 'db://clusterX' ]; 90 $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' ); 91 $this->assertEquals( $active, $esFactory->getProtocols() ); 92 $this->assertEquals( $defaults, $esFactory->getWriteBaseUrls() ); 93 94 $store->clear(); 95 } 96 97 /** 98 * @covers ExternalStoreFactory::getStoreForUrl 99 * @covers ExternalStoreFactory::getStoreLocationFromUrl 100 */ 101 public function testStoreFactoryReadWrite() { 102 $active = [ 'memory' ]; // active store types 103 $defaults = [ 'memory://cluster1', 'memory://cluster2' ]; 104 $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' ); 105 $access = new ExternalStoreAccess( $esFactory ); 106 107 /** @var ExternalStoreMemory $storeLocal */ 108 $storeLocal = $esFactory->getStore( 'memory' ); 109 /** @var ExternalStoreMemory $storeOther */ 110 $storeOther = $esFactory->getStore( 'memory', [ 'domain' => 'other' ] ); 111 $this->assertInstanceOf( ExternalStoreMemory::class, $storeLocal ); 112 $this->assertInstanceOf( ExternalStoreMemory::class, $storeOther ); 113 114 $v1 = wfRandomString(); 115 $v2 = wfRandomString(); 116 $v3 = wfRandomString(); 117 118 $this->assertFalse( $storeLocal->fetchFromURL( 'memory://cluster1/1' ) ); 119 120 $url1 = 'memory://cluster1/1'; 121 $this->assertEquals( 122 $url1, 123 $esFactory->getStoreForUrl( 'memory://cluster1' ) 124 ->store( $esFactory->getStoreLocationFromUrl( 'memory://cluster1' ), $v1 ) 125 ); 126 $this->assertEquals( 127 $v1, 128 $esFactory->getStoreForUrl( 'memory://cluster1/1' ) 129 ->fetchFromURL( 'memory://cluster1/1' ) 130 ); 131 $this->assertEquals( $v1, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) ); 132 133 $url2 = $access->insert( $v2 ); 134 $url3 = $access->insert( $v3, [ 'domain' => 'other' ] ); 135 $this->assertNotFalse( $url2 ); 136 $this->assertNotFalse( $url3 ); 137 // There is only one active store type 138 $this->assertEquals( $v2, $storeLocal->fetchFromURL( $url2 ) ); 139 $this->assertEquals( $v3, $storeOther->fetchFromURL( $url3 ) ); 140 $this->assertFalse( $storeOther->fetchFromURL( $url2 ) ); 141 $this->assertFalse( $storeLocal->fetchFromURL( $url3 ) ); 142 143 $res = $access->fetchFromURLs( [ $url1, $url2, $url3 ] ); 144 $this->assertEquals( [ $url1 => $v1, $url2 => $v2, $url3 => false ], $res, "Local-only" ); 145 146 $storeLocal->clear(); 147 $storeOther->clear(); 148 } 149} 150