Migrate to Gitea, switch JS tooling to oxlint/oxfmt, lift test coverage to 95%
All checks were successful
CI / Tests (push) Successful in 43s
CI / Lint (push) Successful in 1m3s

- 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
This commit is contained in:
2026-05-13 16:51:07 +01:00
parent aa680b25fd
commit 66f0ee9e50
238 changed files with 9243 additions and 1682 deletions

View File

@@ -146,3 +146,76 @@ test('show route displays a single server', function () {
$response->assertInertia(fn (AssertableInertia $page) => $page
->component('servers/Show', false));
});
test('create route fetches and caches locations, server types and images when provider param is given', function () {
$organisation = Organisation::factory()->create();
$provider = Provider::factory()->forOrganisation($organisation)->create([
'type' => ProviderType::HETZNER,
]);
$this->partialMock(HetznerService::class, function (MockInterface $mock) {
$mock->shouldReceive('forProvider')->andReturnSelf();
$mock->shouldReceive('getLocations')->andReturn(collect());
$mock->shouldReceive('getServerTypes')->andReturn(collect());
$mock->shouldReceive('getImages')->andReturn(collect());
});
$response = $this->get(route('servers.create', [
'organisation' => $organisation->id,
'provider' => $provider->id,
]));
$response->assertOk();
$response->assertInertia(fn (AssertableInertia $page) => $page
->component('servers/Create', false)
->has('locations')
->has('serverTypes')
->has('images'));
});
test('store route creates a network when none exists yet for the provider', function () {
$organisation = Organisation::factory()->create();
$provider = Provider::factory()->forOrganisation($organisation)->create([
'type' => ProviderType::HETZNER,
]);
$this->partialMock(HetznerService::class, function (MockInterface $mock) {
$mock->shouldReceive('forProvider')->andReturnSelf();
$mock->shouldReceive('createNetwork')
->once()
->andReturn(new \App\Data\ServerProviders\Network(
id: 'net-99',
name: 'keystone-global',
ipRange: '10.0.0.0/24',
networkZone: 'global',
));
$mock->shouldReceive('createServer')
->once()
->andReturn(new CreatedServer(
name: 'fresh-server',
rootPassword: 'pw',
id: 'srv-1',
status: 'running',
ipv4: '203.0.113.10',
ipv6: '2001:db8::10',
networkId: 'net-99',
privateIp: '10.0.0.10',
));
});
$response = $this->post(route('servers.store', ['organisation' => $organisation->id]), [
'provider' => $provider->id,
'server_type' => 'cx11',
'location' => 'hel1',
'image' => 'ubuntu-22.04',
]);
$response->assertRedirectContains('/servers/');
$this->assertDatabaseHas('networks', [
'provider_id' => $provider->id,
'external_id' => 'net-99',
'name' => 'keystone-global',
]);
});