brandingRepository = new InMemorySchoolBrandingRepository(); $this->logoStorage = new InMemoryLogoStorage(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-02-20 10:00:00'); } }; } #[Test] public function itUploadsLogoAndCreatesBranding(): void { $handler = $this->createHandler(); $file = $this->createTestPngFile(); $command = new UploadLogoCommand( tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, file: $file, ); $branding = $handler($command); self::assertNotNull($branding->logoUrl); self::assertStringStartsWith('https://storage.example.com/logos/', (string) $branding->logoUrl); } #[Test] public function itUploadsLogoToExistingBranding(): void { $this->seedBranding(); $handler = $this->createHandler(); $file = $this->createTestPngFile(); $command = new UploadLogoCommand( tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, file: $file, ); $branding = $handler($command); self::assertNotNull($branding->logoUrl); self::assertStringStartsWith('https://storage.example.com/logos/', (string) $branding->logoUrl); } #[Test] public function itDeletesOldFileWhenReplacingLogo(): void { $this->seedBranding(); self::assertSame(1, $this->logoStorage->count()); $handler = $this->createHandler(); $file = $this->createTestPngFile(); $command = new UploadLogoCommand( tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, file: $file, ); $handler($command); // Old file deleted, new file stored → still 1 self::assertSame(1, $this->logoStorage->count()); self::assertFalse($this->logoStorage->has('logos/old-tenant/old-logo.png')); } #[Test] public function itRecordsDomainEvent(): void { $handler = $this->createHandler(); $file = $this->createTestPngFile(); $command = new UploadLogoCommand( tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, file: $file, ); $branding = $handler($command); $events = $branding->pullDomainEvents(); self::assertCount(1, $events); self::assertInstanceOf(BrandingModifie::class, $events[0]); } #[Test] public function itPersistsBrandingAfterUpload(): void { $handler = $this->createHandler(); $file = $this->createTestPngFile(); $command = new UploadLogoCommand( tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, file: $file, ); $handler($command); $persisted = $this->brandingRepository->findBySchoolId( SchoolId::fromString(self::SCHOOL_ID), TenantId::fromString(self::TENANT_ID), ); self::assertNotNull($persisted); self::assertNotNull($persisted->logoUrl); } #[Test] public function itStoresFileInStorage(): void { $handler = $this->createHandler(); $file = $this->createTestPngFile(); $command = new UploadLogoCommand( tenantId: self::TENANT_ID, schoolId: self::SCHOOL_ID, file: $file, ); $handler($command); self::assertSame(1, $this->logoStorage->count()); } private function createHandler(): UploadLogoHandler { return new UploadLogoHandler( $this->brandingRepository, new LogoUploader($this->logoStorage, new InMemoryImageProcessor()), $this->clock, ); } private function createTestPngFile(): UploadedFile { // Minimal valid PNG (1x1 pixel, transparent) $pngData = base64_decode( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', true, ); $tmpFile = tempnam(sys_get_temp_dir(), 'logo_test_'); if ($tmpFile === false) { self::fail('Failed to create temp file'); } file_put_contents($tmpFile, $pngData); return new UploadedFile( $tmpFile, 'test-logo.png', 'image/png', test: true, ); } private function seedBranding(): void { // Store an old file so we can verify it gets deleted on replacement $this->logoStorage->store('old-content', 'logos/old-tenant/old-logo.png', 'image/png'); $branding = SchoolBranding::creer( schoolId: SchoolId::fromString(self::SCHOOL_ID), tenantId: TenantId::fromString(self::TENANT_ID), createdAt: new DateTimeImmutable('2026-02-01 10:00:00'), ); $branding->changerLogo( new LogoUrl('https://storage.example.com/logos/old-tenant/old-logo.png'), new DateTimeImmutable('2026-02-01 10:00:00'), ); $branding->pullDomainEvents(); $this->brandingRepository->save($branding); } }