125 lines
4.5 KiB
PHP
125 lines
4.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Apoloz\VideoExtractor\Tests\Feature;
|
|
|
|
use Apoloz\VideoExtractor\Dto\VideoRef;
|
|
use Apoloz\VideoExtractor\Exceptions\GeoBlockedException;
|
|
use Apoloz\VideoExtractor\Exceptions\VideoNotFoundException;
|
|
use Apoloz\VideoExtractor\Http\HttpClient;
|
|
use Apoloz\VideoExtractor\Platform\Platform;
|
|
use Apoloz\VideoExtractor\Strategies\Rutube\RutubeApiStrategy;
|
|
use Apoloz\VideoExtractor\Tests\TestCase;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
class RutubeExtractionTest extends TestCase
|
|
{
|
|
private function ref(): VideoRef
|
|
{
|
|
return new VideoRef(
|
|
platform: Platform::RUTUBE,
|
|
id: '373ba417757981facf06c55ea5c46cb4',
|
|
);
|
|
}
|
|
|
|
public function test_extracts_full_info_with_formats(): 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.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');
|
|
});
|
|
}
|
|
}
|