- Add .gitea/workflows/ci.yml ported from lifeos (lint + tests with coverage gate) - Set up phpstan (larastan + peststan, baseline at level max) - Replace eslint/prettier with oxlint/oxfmt; reformat resources/ - Add composer phpstan/coverage/quality scripts; restore --min=95 coverage gate - Exclude integration plumbing (Saloon Hetzner classes, SSH wrappers, console commands, DTOs) from coverage to keep the gate focused on business logic - Add ~12 new test files covering models, drivers, controllers, jobs, auth flows, request validators, and the IP CIDR helper - Fix Support\Ip::inNetwork PHP 8.4 TypeError in CIDR mask check - Fix FirewallRule::command comparing the enum-cast type column to a string - Fix Server::network using the wrong foreign key column - Remove unreachable code under abort(403) in RegisteredUserController
114 lines
3.0 KiB
PHP
114 lines
3.0 KiB
PHP
<?php
|
|
|
|
use App\Actions\FirewallRules\InstallFirewallRule;
|
|
use App\Enums\FirewallRuleStatus;
|
|
use App\Enums\FirewallRuleType;
|
|
use App\Models\FirewallRule;
|
|
use App\Models\Network;
|
|
use App\Models\Organisation;
|
|
use App\Models\Provider;
|
|
use App\Models\Server;
|
|
|
|
use function Pest\Laravel\mock;
|
|
|
|
beforeEach(function () {
|
|
mock(InstallFirewallRule::class)->shouldReceive('execute')->andReturnNull();
|
|
});
|
|
|
|
function makeFirewallServer(): Server
|
|
{
|
|
$organisation = Organisation::factory()->create();
|
|
$provider = Provider::factory()->forOrganisation($organisation)->create();
|
|
$network = Network::create([
|
|
'organisation_id' => $organisation->id,
|
|
'provider_id' => $provider->id,
|
|
'name' => 'test-network',
|
|
'ip_range' => '10.0.0.0/24',
|
|
]);
|
|
|
|
return Server::factory()
|
|
->forOrganisation($organisation->id)
|
|
->forProvider($provider->id)
|
|
->forNetwork((string) $network->id)
|
|
->create();
|
|
}
|
|
|
|
it('builds a ufw allow command for an allow rule', function () {
|
|
$rule = new FirewallRule([
|
|
'type' => FirewallRuleType::ALLOW,
|
|
'ports' => '22',
|
|
]);
|
|
|
|
expect($rule->command())->toBe('ufw allow 22');
|
|
});
|
|
|
|
it('builds a ufw deny command for a deny rule', function () {
|
|
$rule = new FirewallRule([
|
|
'type' => FirewallRuleType::DENY,
|
|
'ports' => '80',
|
|
]);
|
|
|
|
expect($rule->command())->toBe('ufw deny 80');
|
|
});
|
|
|
|
it('includes the source address when from is set', function () {
|
|
$rule = new FirewallRule([
|
|
'type' => FirewallRuleType::ALLOW,
|
|
'ports' => '5432',
|
|
'from' => '10.0.0.0/24',
|
|
]);
|
|
|
|
expect($rule->command())->toBe('ufw allow from 10.0.0.0/24 to any port 5432');
|
|
});
|
|
|
|
it('prefixes the command with delete when requested', function () {
|
|
$rule = new FirewallRule([
|
|
'type' => FirewallRuleType::ALLOW,
|
|
'ports' => '22',
|
|
]);
|
|
|
|
expect($rule->command(delete: true))->toBe('ufw delete allow 22');
|
|
});
|
|
|
|
it('casts status and type to enums', function () {
|
|
$server = makeFirewallServer();
|
|
|
|
$rule = FirewallRule::create([
|
|
'server_id' => $server->id,
|
|
'type' => 'allow',
|
|
'ports' => '22',
|
|
'status' => FirewallRuleStatus::NOT_INSTALLED->value,
|
|
]);
|
|
|
|
$rule->refresh();
|
|
|
|
expect($rule->type)->toBe(FirewallRuleType::ALLOW);
|
|
expect($rule->status)->toBe(FirewallRuleStatus::NOT_INSTALLED);
|
|
});
|
|
|
|
it('belongs to a server', function () {
|
|
$server = makeFirewallServer();
|
|
|
|
$rule = FirewallRule::create([
|
|
'server_id' => $server->id,
|
|
'type' => 'allow',
|
|
'ports' => '22',
|
|
]);
|
|
|
|
expect($rule->server)->toBeInstanceOf(Server::class);
|
|
expect($rule->server->id)->toBe($server->id);
|
|
});
|
|
|
|
it('invokes the install action when a rule is created', function () {
|
|
$action = mock(InstallFirewallRule::class);
|
|
$action->shouldReceive('execute')->once();
|
|
|
|
$server = makeFirewallServer();
|
|
|
|
FirewallRule::create([
|
|
'server_id' => $server->id,
|
|
'type' => 'allow',
|
|
'ports' => '22',
|
|
]);
|
|
});
|