id ?? count($this->campaigns) + 1; $saved = new PreSaveCampaign( id: $id, status: $campaign->status, title: $campaign->title, artistName: $campaign->artistName, coverImageUrl: $campaign->coverImageUrl, releaseDate: $campaign->releaseDate, trackUrls: $campaign->trackUrls, description: $campaign->description, startDate: $campaign->startDate, createdAt: $campaign->createdAt, updatedAt: $campaign->updatedAt ); $this->campaigns[$id] = $saved; return $saved; } public function findById(int $id): ?PreSaveCampaign { return $this->campaigns[$id] ?? null; } public function findAll(array $filters = []): array { return array_values($this->campaigns); } public function findReadyForRelease(): array { return []; } public function findReadyForProcessing(): array { return []; } public function delete(int $id): bool { unset($this->campaigns[$id]); return true; } public function getStatistics(int $campaignId): array { return [ 'total_registrations' => 10, 'completed' => 5, 'pending' => 3, 'failed' => 2, ]; } } // In-Memory Registration Repository class InMemoryRegistrationRepository implements PreSaveRegistrationRepositoryInterface { private array $registrations = []; public function save(PreSaveRegistration $registration): PreSaveRegistration { $id = $registration->id ?? count($this->registrations) + 1; $saved = new PreSaveRegistration( id: $id, campaignId: $registration->campaignId, userId: $registration->userId, platform: $registration->platform, status: $registration->status, registeredAt: $registration->registeredAt, processedAt: $registration->processedAt, errorMessage: $registration->errorMessage, retryCount: $registration->retryCount ); $this->registrations[$id] = $saved; return $saved; } public function findForUserAndCampaign(string $userId, int $campaignId, StreamingPlatform $platform): ?PreSaveRegistration { foreach ($this->registrations as $reg) { if ($reg->userId === $userId && $reg->campaignId === $campaignId && $reg->platform === $platform) { return $reg; } } return null; } public function findByUserId(string $userId): array { return array_values(array_filter($this->registrations, fn($r) => $r->userId === $userId)); } public function findPendingByCampaign(int $campaignId): array { return []; } public function findRetryable(int $campaignId, int $maxRetries): array { return []; } public function getStatusCounts(int $campaignId): array { return [ 'pending' => 3, 'completed' => 5, 'failed' => 2, ]; } public function delete(int $id): bool { unset($this->registrations[$id]); return true; } public function hasRegistered(string $userId, int $campaignId, StreamingPlatform $platform): bool { return $this->findForUserAndCampaign($userId, $campaignId, $platform) !== null; } } // In-Memory OAuth Service class InMemoryOAuthService implements OAuthServiceInterface { private array $providers = []; public function addProvider(string $userId, string $provider): void { $this->providers[$userId . '_' . $provider] = true; } public function getProvider(string $name): \App\Framework\OAuth\OAuthProvider { throw new \RuntimeException('Not implemented in test'); } public function getAuthorizationUrl(string $provider, array $options = []): string { throw new \RuntimeException('Not implemented in test'); } public function handleCallback(string $userId, string $provider, string $code, ?string $state = null): \App\Framework\OAuth\Storage\StoredOAuthToken { throw new \RuntimeException('Not implemented in test'); } public function getTokenForUser(string $userId, string $provider): \App\Framework\OAuth\Storage\StoredOAuthToken { throw new \RuntimeException('Not implemented in test'); } public function refreshToken(\App\Framework\OAuth\Storage\StoredOAuthToken $storedToken): \App\Framework\OAuth\Storage\StoredOAuthToken { throw new \RuntimeException('Not implemented in test'); } public function revokeToken(string $userId, string $provider): bool { throw new \RuntimeException('Not implemented in test'); } public function getUserProfile(string $userId, string $provider): array { throw new \RuntimeException('Not implemented in test'); } public function hasProvider(string $userId, string $provider): bool { return isset($this->providers[$userId . '_' . $provider]); } public function getUserProviders(string $userId): array { throw new \RuntimeException('Not implemented in test'); } public function refreshExpiringTokens(int $withinSeconds = 300): int { throw new \RuntimeException('Not implemented in test'); } public function cleanupExpiredTokens(): int { throw new \RuntimeException('Not implemented in test'); } } // Test the service echo "Testing PreSaveCampaignService...\n\n"; $campaignRepo = new InMemoryCampaignRepository(); $registrationRepo = new InMemoryRegistrationRepository(); $oauthService = new InMemoryOAuthService(); $service = new PreSaveCampaignService( $campaignRepo, $registrationRepo, $oauthService ); // Test 1: Register user successfully echo "Test 1: Register user for campaign\n"; $campaign = PreSaveCampaign::create( title: 'New Album', artistName: 'Test Artist', coverImageUrl: 'https://example.com/cover.jpg', releaseDate: Timestamp::fromDateTime(new DateTimeImmutable('+7 days')), trackUrls: [TrackUrl::create(StreamingPlatform::SPOTIFY, 'track123')] ); $campaign = $campaignRepo->save($campaign->publish()); $oauthService->addProvider('user123', StreamingPlatform::SPOTIFY->getOAuthProvider()); try { $registration = $service->registerUser('user123', $campaign->id, StreamingPlatform::SPOTIFY); echo "✓ User registered successfully\n"; echo " - User ID: {$registration->userId}\n"; echo " - Campaign ID: {$registration->campaignId}\n"; echo " - Platform: {$registration->platform->value}\n"; echo " - Status: {$registration->status->value}\n"; } catch (\Exception $e) { echo "✗ Registration failed: {$e->getMessage()}\n"; } echo "\n"; // Test 2: Check if user can register echo "Test 2: Check if user can register\n"; $canRegister = $service->canUserRegister('user123', $campaign->id, StreamingPlatform::SPOTIFY); echo "Can register (should be false - already registered): " . ($canRegister ? 'YES' : 'NO') . "\n"; echo "\n"; // Test 3: Get campaign stats echo "Test 3: Get campaign statistics\n"; try { $stats = $service->getCampaignStats($campaign->id); echo "✓ Statistics retrieved\n"; echo " - Total registrations: {$stats['total_registrations']}\n"; echo " - Completed: {$stats['completed']}\n"; echo " - Pending: {$stats['pending']}\n"; echo " - Failed: {$stats['failed']}\n"; echo " - Days until release: {$stats['days_until_release']}\n"; } catch (\Exception $e) { echo "✗ Stats failed: {$e->getMessage()}\n"; } echo "\n"; echo "All manual tests completed successfully!\n";