isSubclassOf(ResourceProviderBaseInterface::class)); self::assertTrue((new ReflectionClass(SystemStoreServiceInterface::class))->isSubclassOf(ResourceServiceBaseInterface::class)); } #[Test] public function listingIsAProviderPaginatedIterable(): void { $method = (new ReflectionClass(SystemStoreServiceInterface::class))->getMethod('list'); self::assertSame('iterable', (string) $method->getReturnType()); self::assertCount(1, $method->getParameters()); } #[Test] public function managerContractExposesTenantScopedStoreOperations(): void { $manager = new ReflectionClass(SystemStoreManagerInterface::class); self::assertSame( ['stat', 'read', 'write', 'delete', 'list'], array_map(static fn($method): string => $method->getName(), $manager->getMethods()), ); } #[Test] public function createsSupportedWriteConditions(): void { self::assertSame(WriteCondition::NONE, WriteCondition::none()->mode); self::assertSame(WriteCondition::IF_ABSENT, WriteCondition::ifAbsent()->mode); $condition = WriteCondition::ifMatch('revision-1'); self::assertSame(WriteCondition::IF_MATCH, $condition->mode); self::assertSame('revision-1', $condition->etag); } #[Test] public function rejectsAnEmptyIfMatchEtag(): void { $this->expectException(InvalidArgumentException::class); WriteCondition::ifMatch(''); } #[Test] public function exposesBlobInformation(): void { $metadata = new BlobInfo( key: 'previews/example.webp', mimeType: 'image/webp', size: 123, etag: 'revision-1', modifiedAt: new DateTimeImmutable('2026-08-28T12:00:00+00:00'), attributes: ['variant' => 'popover'], ); self::assertSame('previews/example.webp', $metadata->key); self::assertSame('image/webp', $metadata->mimeType); self::assertSame(123, $metadata->size); self::assertSame('revision-1', $metadata->etag); self::assertSame(['variant' => 'popover'], $metadata->attributes); self::assertNotInstanceOf(\JsonSerializable::class, $metadata); } #[Test] public function parsesLogicalStoresFromTenantConfiguration(): void { $data = [ 'stores' => [ 'previews' => [ 'provider' => 's3', 'service' => 'preview-storage', 'namespace' => 'previews', ], ], ]; $configuration = (new TenantConfiguration())->jsonDeserialize($data); self::assertInstanceOf(StoreReference::class, $configuration->stores()->store('previews')); self::assertSame('s3', $configuration->stores()->store('previews')?->provider); self::assertSame($data['stores'], $configuration->jsonSerialize()['stores']); } #[Test] public function rejectsInvalidLogicalStoreNames(): void { $this->expectException(InvalidArgumentException::class); (new TenantConfiguration())->jsonDeserialize([ 'stores' => [ '../previews' => [ 'provider' => 's3', 'service' => 'preview-storage', 'namespace' => 'previews', ], ], ]); } }