feat(core): add CLI management for tenant system stores

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-08-28 20:56:17 -04:00
parent e137de1183
commit 545a776aeb
8 changed files with 458 additions and 0 deletions
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace KTXT\Unit\SystemStore;
use KTXC\Models\Tenant\TenantConfiguration;
use KTXC\Models\Tenant\TenantObject;
use KTXC\Stores\TenantStore;
use KTXC\SystemStore\SystemStoreConfigurationService;
use KTXF\SystemStore\SystemStoreException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
final class SystemStoreConfigurationServiceTest extends TestCase
{
private TenantStore&MockObject $tenants;
private SystemStoreConfigurationService $configuration;
protected function setUp(): void
{
$this->tenants = $this->createMock(TenantStore::class);
$this->configuration = new SystemStoreConfigurationService($this->tenants);
}
#[Test]
public function listsConfiguredLogicalStores(): void
{
$tenant = (new TenantObject())->setConfiguration(
(new TenantConfiguration())->jsonDeserialize([
'stores' => [
'previews' => [
'provider' => 'local',
'service' => 'default',
'namespace' => 'previews',
],
],
]),
);
$this->tenants->expects(self::once())->method('fetch')->with('tenant-a')->willReturn($tenant);
$stores = $this->configuration->list('tenant-a');
self::assertSame('local', $stores['previews']->provider);
}
#[Test]
public function atomicallyStoresOneLogicalStoreReference(): void
{
$this->tenants->expects(self::once())
->method('storeConfigurationStore')
->with('tenant-a', 'previews', [
'provider' => 'local',
'service' => 'default',
'namespace' => 'previews',
])
->willReturn(true);
$reference = $this->configuration->set(
'tenant-a', 'previews', 'local', 'default', 'previews',
);
self::assertSame('local', $reference->provider);
}
#[Test]
public function reportsAMissingTenantDuringConfiguration(): void
{
$this->tenants->expects(self::once())->method('storeConfigurationStore')->willReturn(false);
$this->expectException(SystemStoreException::class);
$this->configuration->set('missing', 'previews', 'local', 'default', 'previews');
}
#[Test]
public function atomicallyRemovesOneLogicalStoreReference(): void
{
$this->tenants->expects(self::once())
->method('removeConfigurationStore')
->with('tenant-a', 'previews')
->willReturn(true);
self::assertTrue($this->configuration->remove('tenant-a', 'previews'));
}
#[Test]
public function reportsAMissingTenantDuringRemoval(): void
{
$this->tenants->expects(self::once())->method('removeConfigurationStore')->willReturn(null);
$this->expectException(SystemStoreException::class);
$this->configuration->remove('missing', 'previews');
}
#[Test]
public function rejectsUnsafeStoreNamesBeforeBuildingMongoPaths(): void
{
$this->tenants->expects(self::never())->method('storeConfigurationStore');
$this->expectException(\InvalidArgumentException::class);
$this->configuration->set('tenant-a', 'previews.other', 'local', 'default', 'previews');
}
}