sls/tests/Unit/IceCandidateManagerTest.php
kushal.saha 3aca6d90f3 feat(webrtc): implement IceCandidateProviderInterface with Metered and ConvexSol drivers
- 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
2026-08-26 12:44:41 +00:00

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);
}
}