diff --git a/psalm.xml b/psalm.xml index 01ea359..1ec8788 100644 --- a/psalm.xml +++ b/psalm.xml @@ -64,9 +64,6 @@ - - - @@ -74,12 +71,6 @@ - - - - - - diff --git a/src/Core/Cdn/Services/BunnyStorageService.php b/src/Core/Cdn/Services/BunnyStorageService.php index 9447228..03741ef 100644 --- a/src/Core/Cdn/Services/BunnyStorageService.php +++ b/src/Core/Cdn/Services/BunnyStorageService.php @@ -14,8 +14,6 @@ use Bunny\Storage\Client; use Core\Config\ConfigService; use Core\Crypt\LthnHash; -use Core\Service\Contracts\HealthCheckable; -use Core\Service\HealthCheckResult; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; @@ -27,9 +25,11 @@ * - Private zone: DRM/gated content * * Supports vBucket scoping for workspace-isolated CDN paths. - * Implements HealthCheckable for monitoring CDN connectivity. + * Zone reachability is exposed through checkZoneHealth() and isReachable(); + * turning that into a service health report belongs to dappcore/service, + * which is the package that depends on this one. */ -class BunnyStorageService implements HealthCheckable +class BunnyStorageService { protected ?Client $publicClient = null; @@ -555,84 +555,19 @@ public function vBucketList(string $domain, string $path = '', string $zone = 'p return $this->list($scopedPath, $zone); } - // ───────────────────────────────────────────────────────────────────────────── - // Health Check (implements HealthCheckable) - // ───────────────────────────────────────────────────────────────────────────── - - /** - * Perform a health check on the CDN storage zones. - * - * Tests connectivity by listing the root directory of configured storage zones. - * Returns a HealthCheckResult with status, latency, and zone information. - */ - public function healthCheck(): HealthCheckResult - { - $publicConfigured = $this->isConfigured('public'); - $privateConfigured = $this->isConfigured('private'); - - if (! $publicConfigured && ! $privateConfigured) { - return HealthCheckResult::unknown('No CDN storage zones configured'); - } - - $results = []; - $startTime = microtime(true); - $hasError = false; - $isDegraded = false; - - // Check public zone - if ($publicConfigured) { - $publicResult = $this->checkZoneHealth('public'); - $results['public'] = $publicResult; - if (! $publicResult['success']) { - $hasError = true; - } elseif ($publicResult['latency_ms'] > 1000) { - $isDegraded = true; - } - } - - // Check private zone - if ($privateConfigured) { - $privateResult = $this->checkZoneHealth('private'); - $results['private'] = $privateResult; - if (! $privateResult['success']) { - $hasError = true; - } elseif ($privateResult['latency_ms'] > 1000) { - $isDegraded = true; - } - } - - $totalLatency = (microtime(true) - $startTime) * 1000; - - if ($hasError) { - return HealthCheckResult::unhealthy( - 'One or more CDN storage zones are unreachable', - ['zones' => $results], - $totalLatency - ); - } - - if ($isDegraded) { - return HealthCheckResult::degraded( - 'CDN storage zones responding slowly', - ['zones' => $results], - $totalLatency - ); - } - - return HealthCheckResult::healthy( - 'All configured CDN storage zones operational', - ['zones' => $results], - $totalLatency - ); - } - /** * Check health of a specific storage zone. * + * Public because it is the seam the health-reporting decorator in + * dappcore/service consumes. That decorator composes this service rather + * than extending it, so a protected probe would be unreachable and the + * relocation would cost the detail — latency and the error string — that + * makes a health report worth reading. + * * @param string $zone 'public' or 'private' * @return array{success: bool, latency_ms: float, error?: string} */ - protected function checkZoneHealth(string $zone): array + public function checkZoneHealth(string $zone): array { $startTime = microtime(true); @@ -677,7 +612,8 @@ protected function checkZoneHealth(string $zone): array /** * Perform a quick connectivity check. * - * Simpler than healthCheck() - just returns true/false. + * A boolean answer, for callers that only need reachability. The detailed + * per-zone probe is checkZoneHealth(). * * @param string $zone 'public', 'private', or 'any' (default) */ diff --git a/src/Core/Tests/Feature/CdnIntegrationTest.php b/src/Core/Tests/Feature/CdnIntegrationTest.php index c07e3d6..b4abd07 100644 --- a/src/Core/Tests/Feature/CdnIntegrationTest.php +++ b/src/Core/Tests/Feature/CdnIntegrationTest.php @@ -12,6 +12,7 @@ namespace Core\Tests\Feature; use Core\Cdn\Services\AssetPipeline; +use Core\Cdn\Services\BunnyStorageService; use Core\Cdn\Services\CdnUrlBuilder; use Core\Cdn\Services\StorageUrlResolver; use Core\Tests\TestCase; @@ -67,9 +68,24 @@ protected function setUp(): void 'visibility' => 'private', ]); - // Initialize services + // Initialize services. + // + // StorageUrlResolver takes the storage service first and the URL builder + // second, and the builder is the optional one. This passed the builder + // into the first parameter and omitted the required argument entirely, + // so every test in this file died in setUp on a TypeError — 30 of them, + // reported as 30 failures rather than as the one line they are. + // + // BunnyStorageService comes from the container, which is how production + // gets it: Core\Cdn\Boot registers StorageUrlResolver as a singleton and + // lets Laravel autowire the chain. Resolving it here rather than + // hand-building BunnyStorageService(ConfigService) keeps the test on the + // same path the application uses. $this->urlBuilder = new CdnUrlBuilder(); - $this->urlResolver = new StorageUrlResolver($this->urlBuilder); + $this->urlResolver = new StorageUrlResolver( + app(BunnyStorageService::class), + $this->urlBuilder, + ); $this->assetPipeline = new AssetPipeline($this->urlResolver); }