82 lines
3.1 KiB
PHP
82 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Server;
|
|
use App\Services\CommandRunner;
|
|
use App\Services\FleetService;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Mockery;
|
|
use Tests\TestCase;
|
|
|
|
class CommandRunnerTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Mockery::close();
|
|
parent::tearDown();
|
|
}
|
|
|
|
private function server(string $name, string $ip): Server
|
|
{
|
|
return Server::create(['name' => $name, 'ip' => $ip, 'ssh_port' => 22, 'status' => 'online']);
|
|
}
|
|
|
|
public function test_runs_the_command_on_every_server_and_collects_results(): void
|
|
{
|
|
$a = $this->server('a', '10.0.0.1');
|
|
$b = $this->server('b', '10.0.0.2');
|
|
$fleet = Mockery::mock(FleetService::class);
|
|
$fleet->shouldReceive('runPlain')->twice()->andReturn(['ok' => true, 'output' => 'done']);
|
|
|
|
$results = (new CommandRunner($fleet))->run('uptime', new Collection([$a, $b]));
|
|
|
|
$this->assertCount(2, $results);
|
|
$this->assertSame('a', $results[0]['server']);
|
|
$this->assertTrue($results[0]['ok']);
|
|
$this->assertSame('done', $results[0]['output']);
|
|
}
|
|
|
|
public function test_one_server_failing_does_not_abort_the_batch(): void
|
|
{
|
|
$a = $this->server('a', '10.0.0.1');
|
|
$b = $this->server('b', '10.0.0.2');
|
|
$fleet = Mockery::mock(FleetService::class);
|
|
$fleet->shouldReceive('runPlain')->once()->with($a, Mockery::any(), Mockery::any())->andThrow(new \RuntimeException('ssh timeout'));
|
|
$fleet->shouldReceive('runPlain')->once()->with($b, Mockery::any(), Mockery::any())->andReturn(['ok' => true, 'output' => 'ok']);
|
|
|
|
$results = (new CommandRunner($fleet))->run('uptime', new Collection([$a, $b]));
|
|
|
|
$this->assertFalse($results[0]['ok']); // a failed
|
|
$this->assertStringContainsString('ssh timeout', $results[0]['output']);
|
|
$this->assertTrue($results[1]['ok']); // b still ran
|
|
}
|
|
|
|
public function test_a_nonzero_exit_is_reported_as_not_ok(): void
|
|
{
|
|
$a = $this->server('a', '10.0.0.1');
|
|
$fleet = Mockery::mock(FleetService::class);
|
|
$fleet->shouldReceive('runPlain')->once()->andReturn(['ok' => false, 'output' => 'command not found']);
|
|
|
|
$results = (new CommandRunner($fleet))->run('nope', new Collection([$a]));
|
|
|
|
$this->assertFalse($results[0]['ok']);
|
|
}
|
|
|
|
public function test_output_is_byte_capped_and_utf8_scrubbed(): void
|
|
{
|
|
$a = $this->server('a', '10.0.0.1');
|
|
$fleet = Mockery::mock(FleetService::class);
|
|
// 300 KiB of output + an invalid UTF-8 byte → must be capped and scrubbed (no broken snapshot).
|
|
$fleet->shouldReceive('runPlain')->once()->andReturn(['ok' => true, 'output' => str_repeat('x', 300000)."\xB1"]);
|
|
|
|
$results = (new CommandRunner($fleet))->run('cat big', new Collection([$a]));
|
|
|
|
$this->assertLessThanOrEqual(262144, strlen($results[0]['output']));
|
|
$this->assertSame($results[0]['output'], mb_scrub($results[0]['output'], 'UTF-8')); // already valid UTF-8
|
|
}
|
|
}
|