125 lines
4.4 KiB
PHP
125 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Server;
|
|
use App\Services\DockerService;
|
|
use App\Services\FleetService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use InvalidArgumentException;
|
|
use Mockery;
|
|
use Tests\TestCase;
|
|
|
|
class DockerServiceTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Mockery::close();
|
|
parent::tearDown();
|
|
}
|
|
|
|
private function server(): Server
|
|
{
|
|
return Server::create(['name' => 'box', 'ip' => '10.0.0.1', 'ssh_port' => 22, 'status' => 'online']);
|
|
}
|
|
|
|
/** DockerService over a mocked FleetService::runPrivileged; returns [$docker, $fleet]. */
|
|
private function make(): array
|
|
{
|
|
$fleet = Mockery::mock(FleetService::class);
|
|
|
|
return [new DockerService($fleet), $fleet];
|
|
}
|
|
|
|
public function test_containers_parses_the_docker_ps_json(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$json = json_encode(['ID' => 'abc123', 'Names' => 'web', 'Image' => 'nginx', 'State' => 'running', 'Status' => 'Up 2h', 'Ports' => '80/tcp'])."\n"
|
|
.json_encode(['ID' => 'def456', 'Names' => 'db', 'Image' => 'mariadb', 'State' => 'exited', 'Status' => 'Exited (0)', 'Ports' => '']);
|
|
$fleet->shouldReceive('runPrivileged')->once()->andReturn(['ok' => true, 'output' => $json]);
|
|
|
|
$rows = $docker->containers($this->server());
|
|
|
|
$this->assertCount(2, $rows);
|
|
$this->assertSame('web', $rows[0]['name']);
|
|
$this->assertSame('running', $rows[0]['state']);
|
|
$this->assertSame('exited', $rows[1]['state']);
|
|
}
|
|
|
|
public function test_containers_returns_empty_when_docker_is_unavailable(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$fleet->shouldReceive('runPrivileged')->andReturn(['ok' => false, 'output' => 'docker: command not found']);
|
|
|
|
$this->assertSame([], $docker->containers($this->server()));
|
|
}
|
|
|
|
public function test_container_action_runs_the_right_command_for_a_valid_ref(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$fleet->shouldReceive('runPrivileged')->once()
|
|
->with(Mockery::type(Server::class), 'docker restart web1')
|
|
->andReturn(['ok' => true, 'output' => 'web1']);
|
|
|
|
$res = $docker->containerAction($this->server(), 'web1', 'restart');
|
|
|
|
$this->assertTrue($res['ok']);
|
|
}
|
|
|
|
public function test_container_action_rejects_an_unknown_op(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$fleet->shouldReceive('runPrivileged')->never();
|
|
$this->expectException(InvalidArgumentException::class);
|
|
|
|
$docker->containerAction($this->server(), 'web', 'rm -rf');
|
|
}
|
|
|
|
public function test_container_action_rejects_a_ref_with_shell_metacharacters(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$fleet->shouldReceive('runPrivileged')->never();
|
|
$this->expectException(InvalidArgumentException::class);
|
|
|
|
$docker->containerAction($this->server(), 'web; rm -rf /', 'stop');
|
|
}
|
|
|
|
public function test_container_action_rejects_a_leading_dash_ref(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$fleet->shouldReceive('runPrivileged')->never();
|
|
$this->expectException(InvalidArgumentException::class);
|
|
|
|
$docker->containerAction($this->server(), '--help', 'stop'); // arg injection
|
|
}
|
|
|
|
public function test_logs_clamps_the_line_count_and_validates_the_ref(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$fleet->shouldReceive('runPrivileged')->once()
|
|
->with(Mockery::type(Server::class), 'docker logs --tail 2000 web 2>&1 | head -c 262144')
|
|
->andReturn(['ok' => true, 'output' => 'log line']);
|
|
|
|
$this->assertSame('log line', $docker->logs($this->server(), 'web', 99999)); // clamped to 2000, byte-capped
|
|
}
|
|
|
|
public function test_container_action_rejects_a_ref_with_a_trailing_newline(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$fleet->shouldReceive('runPrivileged')->never();
|
|
$this->expectException(InvalidArgumentException::class);
|
|
|
|
$docker->containerAction($this->server(), "web\n", 'stop'); // \z anchor blocks the trailing newline
|
|
}
|
|
|
|
public function test_available_reflects_the_probe(): void
|
|
{
|
|
[$docker, $fleet] = $this->make();
|
|
$fleet->shouldReceive('runPrivileged')->andReturn(['ok' => true, 'output' => 'yes']);
|
|
|
|
$this->assertTrue($docker->available($this->server()));
|
|
}
|
|
}
|