mirror of
https://github.com/stan220/testcontainers-php.git
synced 2026-09-12 10:29:37 +00:00
Adjust StartedGenericContainer
This commit is contained in:
@@ -6,9 +6,11 @@ namespace Testcontainers\Container;
|
|||||||
|
|
||||||
use Docker\API\Client;
|
use Docker\API\Client;
|
||||||
use Docker\API\Model\ContainersIdExecPostBody;
|
use Docker\API\Model\ContainersIdExecPostBody;
|
||||||
|
use Docker\API\Model\ContainersIdJsonGetResponse200;
|
||||||
use Docker\API\Model\IdResponse;
|
use Docker\API\Model\IdResponse;
|
||||||
use Docker\API\Runtime\Client\Client as DockerRuntimeClient;
|
use Docker\API\Runtime\Client\Client as DockerRuntimeClient;
|
||||||
use Docker\Docker;
|
use Docker\Docker;
|
||||||
|
use JsonException;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Testcontainers\ContainerClient\DockerContainerClient;
|
use Testcontainers\ContainerClient\DockerContainerClient;
|
||||||
@@ -99,7 +101,7 @@ class StartedGenericContainer implements StartedTestContainer
|
|||||||
|
|
||||||
public function getHost(): string
|
public function getHost(): string
|
||||||
{
|
{
|
||||||
return $this->inspect()['NetworkSettings']['Gateway'] ?? '127.0.0.1';
|
return '127.0.0.1';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMappedPort(int $port): int
|
public function getMappedPort(int $port): int
|
||||||
@@ -138,7 +140,9 @@ class StartedGenericContainer implements StartedTestContainer
|
|||||||
*/
|
*/
|
||||||
public function getNetworkNames(): array
|
public function getNetworkNames(): array
|
||||||
{
|
{
|
||||||
$networks = $this->inspect()['NetworkSettings']['Networks'] ?? [];
|
/** @var array{NetworkSettings?: array{Networks?: array<string, mixed>}} $inspectData */
|
||||||
|
$inspectData = $this->inspect();
|
||||||
|
$networks = $inspectData['NetworkSettings']['Networks'] ?? [];
|
||||||
return array_keys($networks);
|
return array_keys($networks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,32 +164,73 @@ class StartedGenericContainer implements StartedTestContainer
|
|||||||
throw new RuntimeException("Network with name {$networkName} not exists");
|
throw new RuntimeException("Network with name {$networkName} not exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function inspect(): array
|
/**
|
||||||
|
* @return array<string, mixed> The container details.
|
||||||
|
* @throws RuntimeException If the container inspection fails or the response format is invalid.
|
||||||
|
* TODO: refactor with object after beluga-php/docker-php client library is fixed
|
||||||
|
*/
|
||||||
|
protected function inspect(): array
|
||||||
{
|
{
|
||||||
//For some reason, containerInspect can crash when using FETCH_OBJECT option (e.g. with OpenSearch)
|
try {
|
||||||
//should be checked within beluga-php/docker-php client library
|
/**
|
||||||
|
* For some reason, containerInspect can crash when using FETCH_OBJECT option (e.g. with OpenSearch)
|
||||||
|
* This is a workaround until the issue is fixed (should be checked within beluga-php/docker-php client library)
|
||||||
|
*/
|
||||||
/** @var ResponseInterface | null $containerInspectResponse */
|
/** @var ResponseInterface | null $containerInspectResponse */
|
||||||
$containerInspectResponse = $this->dockerClient->containerInspect($this->id, [], Docker::FETCH_RESPONSE);
|
$containerInspectResponse = $this->dockerClient->containerInspect($this->id, [], $this->dockerClient::FETCH_RESPONSE);
|
||||||
if ($containerInspectResponse === null) {
|
if ($containerInspectResponse === null) {
|
||||||
throw new RuntimeException('Failed to inspect container');
|
throw new RuntimeException('Failed to inspect container: response is null');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Decode the JSON response as an associative array
|
||||||
return json_decode(
|
$decodedResponse = json_decode(
|
||||||
$containerInspectResponse->getBody()->getContents(),
|
$containerInspectResponse->getBody()->getContents(),
|
||||||
true,
|
true,
|
||||||
512,
|
512,
|
||||||
JSON_THROW_ON_ERROR
|
JSON_THROW_ON_ERROR
|
||||||
);
|
);
|
||||||
} catch (Throwable $exception) {
|
|
||||||
throw new RuntimeException('Failed to inspect container', 0, $exception);
|
if (!is_array($decodedResponse)) {
|
||||||
|
throw new RuntimeException('Failed to inspect container: response is not a valid JSON object');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decodedResponse;
|
||||||
|
} catch (JsonException $e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
sprintf('Failed to decode container inspect response: %s', $e->getMessage()),
|
||||||
|
previous: $e
|
||||||
|
);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
sprintf('Unexpected error while inspecting container: %s', $e->getMessage()),
|
||||||
|
previous: $e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ports(): array
|
/**
|
||||||
|
* @return array<string, mixed> An associative array containing the `NetworkSettings` details.
|
||||||
|
* @throws RuntimeException If the container inspection is missing the `NetworkSettings` key.
|
||||||
|
*/
|
||||||
|
protected function networkSettings(): array
|
||||||
|
{
|
||||||
|
$inspectData = $this->inspect();
|
||||||
|
|
||||||
|
if (!isset($inspectData['NetworkSettings']) || !is_array($inspectData['NetworkSettings'])) {
|
||||||
|
throw new RuntimeException('Missing or invalid NetworkSettings in container inspection');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $inspectData['NetworkSettings'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array<array<string, string>>>
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
protected function ports(): array
|
||||||
{
|
{
|
||||||
/** @var array<string, array<array<string, string>>> $ports */
|
/** @var array<string, array<array<string, string>>> $ports */
|
||||||
$ports = $this->inspect()['NetworkSettings']['Ports'] ?? [];
|
$ports = $this->networkSettings()['Ports'] ?? [];
|
||||||
|
|
||||||
if ($ports === []) {
|
if ($ports === []) {
|
||||||
throw new RuntimeException('Failed to get ports from container');
|
throw new RuntimeException('Failed to get ports from container');
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ class MySQLContainer extends GenericContainer
|
|||||||
$this->withWait(new WaitForExec([
|
$this->withWait(new WaitForExec([
|
||||||
"mysqladmin",
|
"mysqladmin",
|
||||||
"ping",
|
"ping",
|
||||||
"-u", "root",
|
|
||||||
"-p{$mysqlRootPassword}",
|
|
||||||
"-h", "127.0.0.1",
|
"-h", "127.0.0.1",
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ abstract class ContainerTestCase extends TestCase
|
|||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
|
if (isset($this->container)) {
|
||||||
$this->container->stop();
|
$this->container->stop();
|
||||||
|
}
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Tests\Integration;
|
||||||
|
|
||||||
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
|
||||||
|
class StartedGenericContainerTest extends ContainerTestCase
|
||||||
|
{
|
||||||
|
public function testShouldReturnContainerId(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('alpine'))
|
||||||
|
->withCommand(['tail', '-f', '/dev/null'])
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
self::assertNotEmpty($container->getId(), 'Container ID should not be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldReturnLastExecId(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('alpine'))
|
||||||
|
->withCommand(['tail', '-f', '/dev/null'])
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$container->exec(['echo', 'Test Exec ID']);
|
||||||
|
|
||||||
|
$lastExecId = $container->getLastExecId();
|
||||||
|
|
||||||
|
self::assertNotNull($lastExecId, 'Last exec ID should not be null');
|
||||||
|
self::assertNotEmpty($lastExecId, 'Last exec ID should not be empty');
|
||||||
|
self::assertMatchesRegularExpression('/^[0-9a-f]+$/', $lastExecId, 'Last exec ID should be a valid hexadecimal string');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldExecuteCommandInContainer(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('alpine'))
|
||||||
|
->withCommand(['tail', '-f', '/dev/null'])
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$output = $container->exec(['echo', 'Hello, Testcontainers!']);
|
||||||
|
self::assertSame('Hello, Testcontainers!', $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldStopContainer(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('alpine'))
|
||||||
|
->withCommand(['tail', '-f', '/dev/null'])
|
||||||
|
->start();
|
||||||
|
|
||||||
|
self::assertNotEmpty($container->getId(), 'Container ID should not be empty');
|
||||||
|
|
||||||
|
$stoppedContainer = $container->stop();
|
||||||
|
|
||||||
|
self::assertNotNull($stoppedContainer, 'Stopped container should not be null');
|
||||||
|
self::assertSame(
|
||||||
|
$container->getId(),
|
||||||
|
$stoppedContainer->getId(),
|
||||||
|
'Stopped container ID should match the original container ID'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertStringContainsString(
|
||||||
|
'No such container',
|
||||||
|
$container->logs(),
|
||||||
|
'Expected message indicating container does not exist'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRestartContainer(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('nginx'))
|
||||||
|
->withExposedPorts(80)
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$containerIdBeforeRestart = $container->getId();
|
||||||
|
$container->restart();
|
||||||
|
$containerIdAfterRestart = $container->getId();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
$containerIdBeforeRestart,
|
||||||
|
$containerIdAfterRestart,
|
||||||
|
'Container ID should remain the same after restart'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRetrieveLogs(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('alpine'))
|
||||||
|
->withCommand(['sh', '-c', 'echo "Hello from logs!" && tail -f /dev/null'])
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$logs = $container->logs();
|
||||||
|
self::assertStringContainsString('Hello from logs!', $logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRetrieveHost(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('alpine'))
|
||||||
|
->withCommand(['tail', '-f', '/dev/null'])
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$host = $container->getHost();
|
||||||
|
self::assertSame('127.0.0.1', $host, 'Host should be 127.0.0.1');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRetrieveFirstMappedPort(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('nginx'))
|
||||||
|
->withExposedPorts(80)
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$mappedPort = $container->getFirstMappedPort();
|
||||||
|
self::assertGreaterThan(0, $mappedPort, 'Mapped port should be greater than 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRetrieveMappedPort(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('nginx'))
|
||||||
|
->withExposedPorts(80)
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$mappedPort = $container->getMappedPort(80);
|
||||||
|
self::assertGreaterThan(0, $mappedPort, 'Mapped port for 80 should be greater than 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRetrieveContainerName(): void
|
||||||
|
{
|
||||||
|
$name = 'test-container-name';
|
||||||
|
$container = (new GenericContainer('alpine'))
|
||||||
|
->withName($name)
|
||||||
|
->withCommand(['tail', '-f', '/dev/null'])
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
self::assertSame($name, $container->getName(), 'Container name should match');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRetrieveLabels(): void
|
||||||
|
{
|
||||||
|
$labels = [
|
||||||
|
'label-1' => 'value-1',
|
||||||
|
'label-2' => 'value-2',
|
||||||
|
];
|
||||||
|
|
||||||
|
$container = (new GenericContainer('alpine'))
|
||||||
|
->withLabels($labels)
|
||||||
|
->withCommand(['tail', '-f', '/dev/null'])
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$retrievedLabels = $container->getLabels();
|
||||||
|
|
||||||
|
self::assertArrayHasKey('label-1', $retrievedLabels);
|
||||||
|
self::assertSame('value-1', $retrievedLabels['label-1']);
|
||||||
|
self::assertArrayHasKey('label-2', $retrievedLabels);
|
||||||
|
self::assertSame('value-2', $retrievedLabels['label-2']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRetrieveNetworkNames(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('nginx'))
|
||||||
|
->withExposedPorts(80)
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$networks = $container->getNetworkNames();
|
||||||
|
|
||||||
|
self::assertNotEmpty($networks, 'Networks should not be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldRetrieveIpAddressFromNetwork(): void
|
||||||
|
{
|
||||||
|
$container = (new GenericContainer('nginx'))
|
||||||
|
->withExposedPorts(80)
|
||||||
|
->start();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
|
||||||
|
$networks = $container->getNetworkNames();
|
||||||
|
$networkName = $networks[0] ?? null;
|
||||||
|
|
||||||
|
self::assertNotNull($networkName, 'Network name should not be null');
|
||||||
|
|
||||||
|
$ipAddress = $container->getIpAddress($networkName);
|
||||||
|
|
||||||
|
self::assertNotEmpty($ipAddress, 'IP address should not be empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user