Files
testcontainers-php/tests/Unit/Utils/HostResolverTest.php
T

254 lines
8.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Testcontainers\Tests\Unit\Utils;
use Docker\Docker;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Testcontainers\Utils\HostResolver;
class HostResolverTest extends TestCase
{
protected function setUp(): void
{
putenv('TESTCONTAINERS_HOST_OVERRIDE');
putenv('DOCKER_HOST');
}
protected function tearDown(): void
{
putenv('TESTCONTAINERS_HOST_OVERRIDE');
putenv('DOCKER_HOST');
}
public function testReturnsTestcontainersHostOverrideFromEnvironment(): void
{
// When the override is set, it should be returned.
putenv('TESTCONTAINERS_HOST_OVERRIDE=tcp://another:2375');
putenv('DOCKER_HOST=tcp://docker:2375');
$dummyClient = $this->createMock(Docker::class);
$resolver = new HostResolver($dummyClient);
$host = $resolver->resolveHost();
$this->assertEquals('tcp://another:2375', $host);
}
public function testReturnsHostnameForTcpProtocols(): void
{
$protocols = ['tcp', 'http', 'https'];
foreach ($protocols as $protocol) {
putenv('DOCKER_HOST=' . $protocol . '://docker:2375');
// Clear any override.
putenv('TESTCONTAINERS_HOST_OVERRIDE');
$dummyClient = $this->createMock(Docker::class);
$resolver = new HostResolver($dummyClient);
$host = $resolver->resolveHost();
$this->assertEquals('docker', $host, "Protocol {$protocol} did not return expected hostname.");
}
}
public function testDoesNotReturnOverrideWhenAllowUserOverridesIsFalse(): void
{
$dummyClient = $this->createMock(Docker::class);
$resolver = new class ($dummyClient) extends HostResolver {
protected function allowUserOverrides(): bool
{
return false;
}
};
putenv('TESTCONTAINERS_HOST_OVERRIDE=tcp://another:2375');
putenv('DOCKER_HOST=tcp://docker:2375');
$host = $resolver->resolveHost();
$this->assertEquals('docker', $host);
}
public function testReturnsLocalhostForUnixAndNpipeProtocolsWhenNotInContainer(): void
{
$dummyClient = $this->createMock(Docker::class);
$resolver = new class ($dummyClient) extends HostResolver {
protected function isInContainer(): bool
{
return false;
}
};
foreach (['unix://docker:2375', 'npipe://docker:2375'] as $uri) {
putenv('DOCKER_HOST=' . $uri);
putenv('TESTCONTAINERS_HOST_OVERRIDE');
$host = $resolver->resolveHost();
$this->assertEquals('localhost', $host, "URI {$uri} should return 'localhost' when not in a container.");
}
}
public function testReturnsHostFromGatewayWhenRunningInContainer(): void
{
// For this test we simulate that we are in a container and the Docker client returns a gateway.
$dockerClient = $this->getMockBuilder(Docker::class)
->disableOriginalConstructor()
->getMock();
// Build a fake network inspection response:
$fakeConfig = new class () {
public function getGateway(): ?string
{
return '172.0.0.1';
}
};
$fakeIPAM = new class ($fakeConfig) {
/** @var object[] */
private array $config;
public function __construct(object $config)
{
$this->config = [$config];
}
/** @return object[] */
public function getConfig(): array
{
return $this->config;
}
};
$fakeNetwork = new class ($fakeIPAM) {
private object $ipam;
public function __construct(object $ipam)
{
$this->ipam = $ipam;
}
public function getIPAM(): object
{
return $this->ipam;
}
};
// Expect that networkInspect will be called with "bridge" (since DOCKER_HOST does not contain "podman.sock")
$dockerClient->expects($this->once())
->method('networkInspect')
->with($this->equalTo('bridge'))
->willReturn($fakeNetwork);
// Override isInContainer() to simulate being inside a container.
$resolver = new class ($dockerClient) extends HostResolver {
protected function isInContainer(): bool
{
return true;
}
};
putenv('DOCKER_HOST=unix://docker:2375');
putenv('TESTCONTAINERS_HOST_OVERRIDE');
$host = $resolver->resolveHost();
$this->assertEquals('172.0.0.1', $host);
}
public function testUsesBridgeNetworkAsGatewayForDockerProvider(): void
{
// For Docker provider (non-Podman) the network used should be "bridge".
$dockerClient = $this->getMockBuilder(Docker::class)
->disableOriginalConstructor()
->getMock();
// Expect networkInspect to be called with "bridge"
$dockerClient->expects($this->once())
->method('networkInspect')
->with($this->equalTo('bridge'))
->willReturn(null); // Simulate not finding a gateway
$resolver = new class ($dockerClient) extends HostResolver {
protected function isInContainer(): bool
{
return true;
}
};
putenv('DOCKER_HOST=unix://docker:2375');
$host = $resolver->resolveHost();
// Since no gateway is found, fallback is "localhost"
$this->assertEquals('localhost', $host);
}
public function testUsesPodmanNetworkAsGatewayForPodmanProvider(): void
{
// For Podman, DOCKER_HOST contains "podman.sock" so the network should be "podman".
$dockerClient = $this->getMockBuilder(Docker::class)
->disableOriginalConstructor()
->getMock();
// Expect networkInspect to be called with "podman"
$dockerClient->expects($this->once())
->method('networkInspect')
->with($this->equalTo('podman'))
->willReturn(null); // Simulate not finding a gateway
$resolver = new class ($dockerClient) extends HostResolver {
protected function isInContainer(): bool
{
return true;
}
};
putenv('DOCKER_HOST=unix://podman.sock');
$host = $resolver->resolveHost();
$this->assertEquals('localhost', $host);
}
public function testReturnsHostFromDefaultGatewayWhenRunningInContainer(): void
{
// Override both findGateway() and findDefaultGateway() to simulate a missing network gateway and a default gateway result.
$dummyClient = $this->createMock(Docker::class);
$resolver = new class ($dummyClient) extends HostResolver {
protected function isInContainer(): bool
{
return true;
}
protected function findGateway(string $networkName): ?string
{
return null;
}
protected function findDefaultGateway(): ?string
{
return '172.0.0.2';
}
};
putenv('DOCKER_HOST=unix://docker:2375');
$host = $resolver->resolveHost();
$this->assertEquals('172.0.0.2', $host);
}
public function testReturnsLocalhostIfUnableToFindGateway(): void
{
// Override to simulate that neither network inspection nor default gateway yield a result.
$dummyClient = $this->createMock(Docker::class);
$resolver = new class ($dummyClient) extends HostResolver {
protected function isInContainer(): bool
{
return true;
}
protected function findGateway(string $networkName): ?string
{
return null;
}
protected function findDefaultGateway(): ?string
{
return null;
}
};
putenv('DOCKER_HOST=unix://docker:2375');
$host = $resolver->resolveHost();
$this->assertEquals('localhost', $host);
}
public function testThrowsForUnsupportedProtocol(): void
{
putenv('DOCKER_HOST=invalid://unknown');
$dummyClient = $this->createMock(Docker::class);
$resolver = new HostResolver($dummyClient);
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage("Unsupported Docker host scheme: invalid");
$resolver->resolveHost();
}
}