Http::response($this->fixture('rutube/video_meta.json'), 200), 'rutube.ru/api/play/options/*' => Http::response($this->fixture('rutube/play_options.json'), 200), ]); $strategy = new RutubeApiStrategy($this->app->make(HttpClient::class)); $info = $strategy->extract($this->ref()); self::assertSame('373ba417757981facf06c55ea5c46cb4', $info->id); self::assertSame('First words of a toddler. Big collection.', $info->title); self::assertSame(3756, $info->duration); self::assertSame(6048782, $info->viewCount); self::assertSame('Alena Vasilieva', $info->uploader); self::assertSame('40529538', $info->uploaderId); self::assertSame(['Education'], $info->categories); self::assertSame(0, $info->ageLimit); self::assertFalse($info->isLive); // created_ts "2025-08-06T14:23:14" treated as UTC. self::assertSame(strtotime('2025-08-06T14:23:14 UTC'), $info->timestamp); // Thumbnail URL is stable. self::assertStringContainsString('pic.rtbcdn.ru/video', (string) $info->thumbnailUrl()); // Formats: video_balancer has default+m3u8 (deduplicated to one) -> 1 format. self::assertCount(1, $info->formats); self::assertSame('hls_m3u8', $info->formats[0]->formatId); self::assertSame(1784295930, $info->formats[0]->expiresAt); self::assertSame(1784295930, $info->formatExpiresAt); } public function test_thumbnail_only_uses_single_request(): void { Http::fakeSequence()->push( $this->fixture('rutube/video_meta.json'), 200, ); $strategy = new RutubeApiStrategy($this->app->make(HttpClient::class)); $url = $strategy->extractThumbnail($this->ref()); self::assertSame('https://pic.rtbcdn.ru/video/2025-08-07/0a/5f/0a5f3ef60eb641935f678c5f543a92fe.jpg', $url); } public function test_throws_when_meta_returns_404(): void { Http::fake([ 'rutube.ru/api/video/*' => Http::response($this->fixture('rutube/video_meta_404.json'), 404), ]); $strategy = new RutubeApiStrategy($this->app->make(HttpClient::class)); $this->expectException(VideoNotFoundException::class); $strategy->extract($this->ref()); } public function test_throws_geo_blocked_when_acl_denies(): void { Http::fake([ 'rutube.ru/api/video/*' => Http::response($this->fixture('rutube/video_meta.json'), 200), 'rutube.ru/api/play/options/*' => Http::response( $this->fixture('rutube/play_options_geo_blocked.json'), 200, ), ]); $strategy = new RutubeApiStrategy($this->app->make(HttpClient::class)); $this->expectException(GeoBlockedException::class); $this->expectExceptionMessage('Not available in your region'); $strategy->extract($this->ref()); } public function test_private_token_is_forwarded_to_api(): void { Http::fake([ 'rutube.ru/api/video/*' => Http::response($this->fixture('rutube/video_meta.json'), 200), ]); Http::fakeSequence()->push($this->fixture('rutube/video_meta.json'), 200); $ref = new VideoRef( platform: Platform::RUTUBE, id: '884fb55f07a97ab673c7d654553e0f48', query: ['p' => 'PRIVATE_TOKEN'], ); $strategy = new RutubeApiStrategy($this->app->make(HttpClient::class)); $url = $strategy->extractThumbnail($ref); self::assertNotNull($url); // The Http::fakeSequence captured the request; assert the token was sent. Http::assertSent(function ($request): bool { return str_contains($request->url(), 'p=PRIVATE_TOKEN'); }); } }