3 * @copyright Copyright (C) 2010-2021, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Test\src\Core;
25 use Friendica\Core\Config\IConfig;
26 use Friendica\Core\Config\PreloadConfig;
27 use Friendica\Core\Hook;
28 use Friendica\Core\L10n;
29 use Friendica\Core\Session\ISession;
30 use Friendica\Core\StorageManager;
31 use Friendica\Database\Database;
33 use Friendica\Factory\ConfigFactory;
34 use Friendica\Model\Config\Config;
35 use Friendica\Model\Storage;
36 use Friendica\Core\Session;
37 use Friendica\Network\HTTPClient;
38 use Friendica\Test\DatabaseTest;
39 use Friendica\Test\Util\Database\StaticDatabase;
40 use Friendica\Test\Util\VFSTrait;
41 use Friendica\Util\ConfigFileLoader;
42 use Friendica\Util\Profiler;
43 use org\bovigo\vfs\vfsStream;
44 use Psr\Log\LoggerInterface;
45 use Psr\Log\NullLogger;
46 use Friendica\Test\Util\SampleStorageBackend;
48 class StorageManagerTest extends DatabaseTest
55 /** @var LoggerInterface */
59 /** @var HTTPClient */
62 protected function setUp(): void
68 vfsStream::newDirectory(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER, 0777)->at($this->root);
70 $this->logger = new NullLogger();
72 $profiler = \Mockery::mock(Profiler::class);
73 $profiler->shouldReceive('startRecording');
74 $profiler->shouldReceive('stopRecording');
75 $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
77 // load real config to avoid mocking every config-entry which is related to the Database class
78 $configFactory = new ConfigFactory();
79 $loader = $configFactory->createConfigFileLoader($this->root->url(), []);
80 $configCache = $configFactory->createCache($loader);
82 $this->dba = new StaticDatabase($configCache, $profiler, $this->logger);
84 $configModel = new Config($this->dba);
85 $this->config = new PreloadConfig($configCache, $configModel);
86 $this->config->set('storage', 'name', 'Database');
87 $this->config->set('storage', 'filesystem_path', $this->root->getChild(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
89 $this->l10n = \Mockery::mock(L10n::class);
91 $this->httpRequest = \Mockery::mock(HTTPClient::class);
94 protected function tearDown(): void
96 $this->root->removeChild(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER);
102 * Test plain instancing first
104 public function testInstance()
106 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest);
108 self::assertInstanceOf(StorageManager::class, $storageManager);
111 public function dataStorages()
117 'interface' => Storage\IStorage::class,
122 'name' => Storage\Database::NAME,
124 'interface' => Storage\IWritableStorage::class,
125 'assert' => Storage\Database::class,
126 'assertName' => Storage\Database::NAME,
129 'name' => Storage\Filesystem::NAME,
131 'interface' => Storage\IWritableStorage::class,
132 'assert' => Storage\Filesystem::class,
133 'assertName' => Storage\Filesystem::NAME,
135 'systemresource' => [
136 'name' => Storage\SystemResource::NAME,
138 'interface' => Storage\IStorage::class,
139 'assert' => Storage\SystemResource::class,
140 'assertName' => Storage\SystemResource::NAME,
148 'userBackend' => false,
154 * Test the getByName() method
156 * @dataProvider dataStorages
158 public function testGetByName($name, $valid, $interface, $assert, $assertName)
161 $this->expectException(Storage\InvalidClassStorageException::class);
164 if ($interface === Storage\IWritableStorage::class) {
165 $this->config->set('storage', 'name', $name);
168 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
170 if ($interface === Storage\IWritableStorage::class) {
171 $storage = $storageManager->getWritableStorageByName($name);
173 $storage = $storageManager->getByName($name);
176 self::assertInstanceOf($interface, $storage);
177 self::assertInstanceOf($assert, $storage);
178 self::assertEquals($assertName, $storage);
182 * Test the isValidBackend() method
184 * @dataProvider dataStorages
186 public function testIsValidBackend($name, $valid, $interface, $assert, $assertName)
188 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
190 // true in every of the backends
191 self::assertEquals(!empty($assertName), $storageManager->isValidBackend($name));
193 // if it's a IWritableStorage, the valid backend should return true, otherwise false
194 self::assertEquals($interface === Storage\IWritableStorage::class, $storageManager->isValidBackend($name, StorageManager::DEFAULT_BACKENDS));
198 * Test the method listBackends() with default setting
200 public function testListBackends()
202 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
204 self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
208 * Test the method getBackend()
210 * @dataProvider dataStorages
212 public function testGetBackend($name, $valid, $interface, $assert, $assertName)
214 if ($interface !== Storage\IWritableStorage::class) {
215 static::markTestSkipped('only works for IWritableStorage');
218 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
220 $selBackend = $storageManager->getWritableStorageByName($name);
221 $storageManager->setBackend($selBackend);
223 self::assertInstanceOf($assert, $storageManager->getBackend());
227 * Test the method getBackend() with a pre-configured backend
229 * @dataProvider dataStorages
231 public function testPresetBackend($name, $valid, $interface, $assert, $assertName)
233 $this->config->set('storage', 'name', $name);
234 if ($interface !== Storage\IWritableStorage::class) {
235 $this->expectException(Storage\InvalidClassStorageException::class);
238 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
240 self::assertInstanceOf($assert, $storageManager->getBackend());
244 * Tests the register and unregister methods for a new backend storage class
246 * Uses a sample storage for testing
248 * @see SampleStorageBackend
250 public function testRegisterUnregisterBackends()
252 /// @todo Remove dice once "Hook" is dynamic and mockable
254 ->addRules(include __DIR__ . '/../../../static/dependencies.config.php')
255 ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
256 ->addRule(ISession::class, ['instanceOf' => Session\Memory::class, 'shared' => true, 'call' => null]);
259 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
261 self::assertTrue($storageManager->register(SampleStorageBackend::class));
263 self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
264 SampleStorageBackend::getName(),
265 ]), $storageManager->listBackends());
266 self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
267 SampleStorageBackend::getName()
268 ]), $this->config->get('storage', 'backends'));
270 self::assertTrue($storageManager->unregister(SampleStorageBackend::class));
271 self::assertEquals(StorageManager::DEFAULT_BACKENDS, $this->config->get('storage', 'backends'));
272 self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
276 * tests that an active backend cannot get unregistered
278 public function testUnregisterActiveBackend()
280 /// @todo Remove dice once "Hook" is dynamic and mockable
282 ->addRules(include __DIR__ . '/../../../static/dependencies.config.php')
283 ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
284 ->addRule(ISession::class, ['instanceOf' => Session\Memory::class, 'shared' => true, 'call' => null]);
287 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
289 self::assertTrue($storageManager->register(SampleStorageBackend::class));
291 self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
292 SampleStorageBackend::getName(),
293 ]), $storageManager->listBackends());
294 self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
295 SampleStorageBackend::getName()
296 ]), $this->config->get('storage', 'backends'));
298 // inline call to register own class as hook (testing purpose only)
299 SampleStorageBackend::registerHook();
302 self::assertTrue($storageManager->setBackend($storageManager->getWritableStorageByName(SampleStorageBackend::NAME)));
303 self::assertEquals(SampleStorageBackend::NAME, $this->config->get('storage', 'name'));
305 self::assertInstanceOf(SampleStorageBackend::class, $storageManager->getBackend());
307 self::expectException(Storage\StorageException::class);
308 self::expectExceptionMessage('Cannot unregister Sample Storage, because it\'s currently active.');
310 $storageManager->unregister(SampleStorageBackend::class);
314 * Test moving data to a new storage (currently testing db & filesystem)
316 * @dataProvider dataStorages
318 public function testMoveStorage($name, $valid, $interface, $assert, $assertName)
320 if ($interface !== Storage\IWritableStorage::class) {
321 self::markTestSkipped("No user backend");
324 $this->loadFixture(__DIR__ . '/../../datasets/storage/database.fixture.php', $this->dba);
326 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
327 $storage = $storageManager->getWritableStorageByName($name);
328 $storageManager->move($storage);
330 $photos = $this->dba->select('photo', ['backend-ref', 'backend-class', 'id', 'data']);
332 while ($photo = $this->dba->fetch($photos)) {
333 self::assertEmpty($photo['data']);
335 $storage = $storageManager->getByName($photo['backend-class']);
336 $data = $storage->get($photo['backend-ref']);
338 self::assertNotEmpty($data);
343 * Test moving data to a WRONG storage
345 public function testWrongWritableStorage()
347 $this->expectException(Storage\InvalidClassStorageException::class);
348 $this->expectExceptionMessage('Backend SystemResource is not valid');
350 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
351 $storage = $storageManager->getWritableStorageByName(Storage\SystemResource::getName());
352 $storageManager->move($storage);