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\Storage\Repository;
25 use Friendica\Core\Config\Capability\IManageConfigValues;
26 use Friendica\Core\Config\Type\PreloadConfig;
27 use Friendica\Core\Hook;
28 use Friendica\Core\L10n;
29 use Friendica\Core\Session\Capability\IHandleSessions;
30 use Friendica\Core\Session\Type\Memory;
31 use Friendica\Core\Storage\Exception\InvalidClassStorageException;
32 use Friendica\Core\Storage\Capability\ICanReadFromStorage;
33 use Friendica\Core\Storage\Capability\ICanWriteToStorage;
34 use Friendica\Core\Storage\Exception\StorageException;
35 use Friendica\Core\Storage\Repository\StorageManager;
36 use Friendica\Core\Storage\Type\Filesystem;
37 use Friendica\Core\Storage\Type\SystemResource;
38 use Friendica\Database\Database;
40 use Friendica\Core\Config\Factory\Config;
41 use Friendica\Core\Config\Repository;
42 use Friendica\Core\Storage\Type;
43 use Friendica\Network\HTTPClient;
44 use Friendica\Test\DatabaseTest;
45 use Friendica\Test\Util\Database\StaticDatabase;
46 use Friendica\Test\Util\VFSTrait;
47 use Friendica\Util\Profiler;
48 use org\bovigo\vfs\vfsStream;
49 use Psr\Log\LoggerInterface;
50 use Psr\Log\NullLogger;
51 use Friendica\Test\Util\SampleStorageBackend;
53 class StorageManagerTest extends DatabaseTest
58 /** @var IManageConfigValues */
60 /** @var LoggerInterface */
64 /** @var HTTPClient */
67 protected function setUp(): void
73 vfsStream::newDirectory(Type\FilesystemConfig::DEFAULT_BASE_FOLDER, 0777)->at($this->root);
75 $this->logger = new NullLogger();
77 $profiler = \Mockery::mock(Profiler::class);
78 $profiler->shouldReceive('startRecording');
79 $profiler->shouldReceive('stopRecording');
80 $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
82 // load real config to avoid mocking every config-entry which is related to the Database class
83 $configFactory = new Config();
84 $loader = $configFactory->createConfigFileLoader($this->root->url(), []);
85 $configCache = $configFactory->createCache($loader);
87 $this->dba = new StaticDatabase($configCache, $profiler, $this->logger);
89 $configModel = new Repository\Config($this->dba);
90 $this->config = new PreloadConfig($configCache, $configModel);
91 $this->config->set('storage', 'name', 'Database');
92 $this->config->set('storage', 'filesystem_path', $this->root->getChild(Type\FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
94 $this->l10n = \Mockery::mock(L10n::class);
96 $this->httpRequest = \Mockery::mock(HTTPClient::class);
99 protected function tearDown(): void
101 $this->root->removeChild(Type\FilesystemConfig::DEFAULT_BASE_FOLDER);
107 * Test plain instancing first
109 public function testInstance()
111 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest);
113 self::assertInstanceOf(StorageManager::class, $storageManager);
116 public function dataStorages()
122 'interface' => ICanReadFromStorage::class,
127 'name' => Type\Database::NAME,
129 'interface' => ICanWriteToStorage::class,
130 'assert' => Type\Database::class,
131 'assertName' => Type\Database::NAME,
134 'name' => Filesystem::NAME,
136 'interface' => ICanWriteToStorage::class,
137 'assert' => Filesystem::class,
138 'assertName' => Filesystem::NAME,
140 'systemresource' => [
141 'name' => SystemResource::NAME,
143 'interface' => ICanReadFromStorage::class,
144 'assert' => SystemResource::class,
145 'assertName' => SystemResource::NAME,
153 'userBackend' => false,
159 * Test the getByName() method
161 * @dataProvider dataStorages
163 public function testGetByName($name, $valid, $interface, $assert, $assertName)
166 $this->expectException(InvalidClassStorageException::class);
169 if ($interface === ICanWriteToStorage::class) {
170 $this->config->set('storage', 'name', $name);
173 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
175 if ($interface === ICanWriteToStorage::class) {
176 $storage = $storageManager->getWritableStorageByName($name);
178 $storage = $storageManager->getByName($name);
181 self::assertInstanceOf($interface, $storage);
182 self::assertInstanceOf($assert, $storage);
183 self::assertEquals($assertName, $storage);
187 * Test the isValidBackend() method
189 * @dataProvider dataStorages
191 public function testIsValidBackend($name, $valid, $interface, $assert, $assertName)
193 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
195 // true in every of the backends
196 self::assertEquals(!empty($assertName), $storageManager->isValidBackend($name));
198 // if it's a ICanWriteToStorage, the valid backend should return true, otherwise false
199 self::assertEquals($interface === ICanWriteToStorage::class, $storageManager->isValidBackend($name, StorageManager::DEFAULT_BACKENDS));
203 * Test the method listBackends() with default setting
205 public function testListBackends()
207 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
209 self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
213 * Test the method getBackend()
215 * @dataProvider dataStorages
217 public function testGetBackend($name, $valid, $interface, $assert, $assertName)
219 if ($interface !== ICanWriteToStorage::class) {
220 static::markTestSkipped('only works for ICanWriteToStorage');
223 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
225 $selBackend = $storageManager->getWritableStorageByName($name);
226 $storageManager->setBackend($selBackend);
228 self::assertInstanceOf($assert, $storageManager->getBackend());
232 * Test the method getBackend() with a pre-configured backend
234 * @dataProvider dataStorages
236 public function testPresetBackend($name, $valid, $interface, $assert, $assertName)
238 $this->config->set('storage', 'name', $name);
239 if ($interface !== ICanWriteToStorage::class) {
240 $this->expectException(InvalidClassStorageException::class);
243 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
245 self::assertInstanceOf($assert, $storageManager->getBackend());
249 * Tests the register and unregister methods for a new backend storage class
251 * Uses a sample storage for testing
253 * @see SampleStorageBackend
255 public function testRegisterUnregisterBackends()
257 /// @todo Remove dice once "Hook" is dynamic and mockable
259 ->addRules(include __DIR__ . '/../../../../../static/dependencies.config.php')
260 ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
261 ->addRule(IHandleSessions::class, ['instanceOf' => Memory::class, 'shared' => true, 'call' => null]);
264 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
266 self::assertTrue($storageManager->register(SampleStorageBackend::class));
268 self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
269 SampleStorageBackend::getName(),
270 ]), $storageManager->listBackends());
271 self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
272 SampleStorageBackend::getName()
273 ]), $this->config->get('storage', 'backends'));
275 self::assertTrue($storageManager->unregister(SampleStorageBackend::class));
276 self::assertEquals(StorageManager::DEFAULT_BACKENDS, $this->config->get('storage', 'backends'));
277 self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
281 * tests that an active backend cannot get unregistered
283 public function testUnregisterActiveBackend()
285 /// @todo Remove dice once "Hook" is dynamic and mockable
287 ->addRules(include __DIR__ . '/../../../../../static/dependencies.config.php')
288 ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
289 ->addRule(IHandleSessions::class, ['instanceOf' => Memory::class, 'shared' => true, 'call' => null]);
292 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
294 self::assertTrue($storageManager->register(SampleStorageBackend::class));
296 self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
297 SampleStorageBackend::getName(),
298 ]), $storageManager->listBackends());
299 self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
300 SampleStorageBackend::getName()
301 ]), $this->config->get('storage', 'backends'));
303 // inline call to register own class as hook (testing purpose only)
304 SampleStorageBackend::registerHook();
307 self::assertTrue($storageManager->setBackend($storageManager->getWritableStorageByName(SampleStorageBackend::NAME)));
308 self::assertEquals(SampleStorageBackend::NAME, $this->config->get('storage', 'name'));
310 self::assertInstanceOf(SampleStorageBackend::class, $storageManager->getBackend());
312 self::expectException(StorageException::class);
313 self::expectExceptionMessage('Cannot unregister Sample Storage, because it\'s currently active.');
315 $storageManager->unregister(SampleStorageBackend::class);
319 * Test moving data to a new storage (currently testing db & filesystem)
321 * @dataProvider dataStorages
323 public function testMoveStorage($name, $valid, $interface, $assert, $assertName)
325 if ($interface !== ICanWriteToStorage::class) {
326 self::markTestSkipped("No user backend");
329 $this->loadFixture(__DIR__ . '/../../../../datasets/storage/database.fixture.php', $this->dba);
331 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
332 $storage = $storageManager->getWritableStorageByName($name);
333 $storageManager->move($storage);
335 $photos = $this->dba->select('photo', ['backend-ref', 'backend-class', 'id', 'data']);
337 while ($photo = $this->dba->fetch($photos)) {
338 self::assertEmpty($photo['data']);
340 $storage = $storageManager->getByName($photo['backend-class']);
341 $data = $storage->get($photo['backend-ref']);
343 self::assertNotEmpty($data);
348 * Test moving data to a WRONG storage
350 public function testWrongWritableStorage()
352 $this->expectException(InvalidClassStorageException::class);
353 $this->expectExceptionMessage('Backend SystemResource is not valid');
355 $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
356 $storage = $storageManager->getWritableStorageByName(SystemResource::getName());
357 $storageManager->move($storage);