- Add Metered and ConvexSol (CoTURN) provider drivers with response validation and caching - Add IceCandidateManager with automatic failover and structured logging - Add IceCandidateProviderInterface and DTOs conforming to RTCIceServer format
135 lines
4.5 KiB
PHP
135 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit;
|
|
|
|
use App\Contracts\IceCandidateProviderInterface;
|
|
use App\DTOs\IceCandidatesDto;
|
|
use App\Services\IceCandidate\IceCandidateManager;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use RuntimeException;
|
|
use Tests\TestCase;
|
|
|
|
class IceCandidateManagerTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Cache::flush();
|
|
$this->app->forgetInstance(IceCandidateManager::class);
|
|
}
|
|
|
|
public function test_manager_resolves_metered_as_default_driver(): void
|
|
{
|
|
Http::fake([
|
|
'*metered*' => Http::response([
|
|
['urls' => 'stun:stun.relay.metered.ca:80'],
|
|
], 200),
|
|
]);
|
|
|
|
$manager = app(IceCandidateManager::class);
|
|
$candidates = $manager->getIceCandidates();
|
|
|
|
$this->assertCount(1, $candidates);
|
|
$this->assertEquals('stun:stun.relay.metered.ca:80', $candidates->toArray()[0]['urls']);
|
|
}
|
|
|
|
public function test_manager_resolves_convexsol_when_configured(): void
|
|
{
|
|
config([
|
|
'services.ice.default' => 'convexsol',
|
|
'services.ice.providers.convexsol' => [
|
|
'username' => 'convex_usr',
|
|
'password' => 'convex_pwd',
|
|
'turn_urls' => ['turn:turn.convexsol.com:3478'],
|
|
'stun_urls' => ['stun:turn.convexsol.com:3478'],
|
|
],
|
|
]);
|
|
|
|
$manager = app(IceCandidateManager::class);
|
|
$candidates = $manager->getIceCandidates();
|
|
|
|
$this->assertCount(2, $candidates);
|
|
$array = $candidates->toArray();
|
|
$this->assertEquals('convex_usr', $array[1]['username']);
|
|
$this->assertEquals('convex_pwd', $array[1]['credential']);
|
|
}
|
|
|
|
public function test_manager_falls_back_and_logs_warning_when_primary_fails(): void
|
|
{
|
|
Log::shouldReceive('warning')
|
|
->once()
|
|
->withArgs(function ($message, $context) {
|
|
return str_contains($message, 'Primary ICE candidate provider [metered] failed') &&
|
|
$context['primary_driver'] === 'metered' &&
|
|
$context['fallback_driver'] === 'convexsol';
|
|
});
|
|
|
|
// Fail primary Metered HTTP request
|
|
Http::fake([
|
|
'*metered*' => Http::response('Gateway Error', 502),
|
|
]);
|
|
|
|
config([
|
|
'services.ice.default' => 'metered',
|
|
'services.ice.fallback' => 'convexsol',
|
|
'services.ice.providers.convexsol' => [
|
|
'username' => 'fallback_user',
|
|
'password' => 'fallback_password',
|
|
'turn_urls' => ['turn:turn.convexsol.com:3478'],
|
|
'stun_urls' => ['stun:turn.convexsol.com:3478'],
|
|
],
|
|
]);
|
|
|
|
$manager = app(IceCandidateManager::class);
|
|
$candidates = $manager->getIceCandidates();
|
|
|
|
$this->assertCount(2, $candidates);
|
|
$array = $candidates->toArray();
|
|
$this->assertEquals('fallback_user', $array[1]['username']);
|
|
}
|
|
|
|
public function test_manager_falls_back_to_default_stun_and_logs_error_when_both_fail(): void
|
|
{
|
|
Log::shouldReceive('warning')->once();
|
|
Log::shouldReceive('error')
|
|
->once()
|
|
->withArgs(function ($message) {
|
|
return str_contains($message, 'Fallback ICE candidate provider [convexsol] also failed');
|
|
});
|
|
|
|
// Fail primary Metered HTTP request
|
|
Http::fake([
|
|
'*metered*' => Http::response('Gateway Error', 502),
|
|
]);
|
|
|
|
$manager = app(IceCandidateManager::class);
|
|
$manager->extend('convexsol', function () {
|
|
return new class implements IceCandidateProviderInterface
|
|
{
|
|
public function getIceCandidates(): IceCandidatesDto
|
|
{
|
|
throw new RuntimeException('ConvexSol Server Error');
|
|
}
|
|
};
|
|
});
|
|
|
|
config([
|
|
'services.ice.default' => 'metered',
|
|
'services.ice.fallback' => 'convexsol',
|
|
]);
|
|
|
|
$candidates = $manager->getIceCandidates();
|
|
|
|
$this->assertCount(1, $candidates);
|
|
$this->assertContains('stun:stun.l.google.com:19302', $candidates->toArray()[0]['urls']);
|
|
}
|
|
|
|
public function test_container_resolves_interface_to_manager(): void
|
|
{
|
|
$resolved = app(IceCandidateProviderInterface::class);
|
|
$this->assertInstanceOf(IceCandidateManager::class, $resolved);
|
|
}
|
|
}
|