Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,28 @@
use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Mount;
use App\Services\Acl\Api\AdminAcl;
use Dedoc\Scramble\Attributes\BodyParameter;
use Illuminate\Contracts\Validation\ValidationRule;

// The rules come straight off the model, so there is no rules array to hang comments on.
// Attributes are not inherited, so UpdateMountRequest repeats them against the same descriptions.
#[BodyParameter('name', description: 'Name the mount is shown under in the Panel.')]
#[BodyParameter('description', description: 'Free form text describing the mount.')]
#[BodyParameter('source', description: 'Absolute path on the node that is mounted into the server.')]
#[BodyParameter('target', description: 'Absolute path inside the server container the source appears at.')]
#[BodyParameter('read_only', description: 'Mount the source read only so servers cannot write to it.')]
#[BodyParameter('user_mountable', description: 'Whether server owners may attach this mount themselves.')]
class StoreMountRequest extends ApplicationApiRequest
{
protected ?string $resource = Mount::RESOURCE_NAME;

protected int $permission = AdminAcl::WRITE;

/**
* @return array<string, string|array<string|\Stringable|ValidationRule>>
*/
public function rules(): array
{
return Mount::getRules();
}
}
11 changes: 11 additions & 0 deletions app/Models/Mount.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ class Mount extends Model implements Validatable
*/
protected $guarded = ['id'];

/**
* The database columns carry no defaults, so creating a mount without
* these booleans (the API marks them 'sometimes') would fail the insert.
*
* @var array<string, bool>
*/
protected $attributes = [
'read_only' => false,
'user_mountable' => false,
];

/**
* Rules verifying that the data being stored matches the expectations of the database.
*
Expand Down
22 changes: 22 additions & 0 deletions tests/Feature/InstallerRedirectTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

it('redirects panel routes to the installer when not installed', function () {
config(['app.installed' => false]);

$this->get('/admin')->assertRedirect(route('installer'));
});

it('does not redirect to the installer when installed', function () {
config(['app.installed' => true]);

// A guest hits the auth layer instead of being sent to the installer.
$response = $this->get('/admin');

expect($response->headers->get('Location'))->not->toBe(route('installer'));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

it('allows the installer route itself when not installed', function () {
config(['app.installed' => false]);

$this->get(route('installer'))->assertOk();
});
80 changes: 80 additions & 0 deletions tests/Integration/Api/Application/AllocationControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace App\Tests\Integration\Api\Application;

use App\Models\Allocation;
use App\Models\Node;
use App\Services\Acl\Api\AdminAcl;
use Illuminate\Http\Response;

class AllocationControllerTest extends ApplicationApiIntegrationTestCase
{
private Node $node;

protected function setUp(): void
{
parent::setUp();

$this->node = Node::factory()->create();
}

public function test_list_node_allocations(): void
{
$allocations = Allocation::factory(2)->create(['node_id' => $this->node->id]);

$response = $this->getJson("/api/application/nodes/{$this->node->id}/allocations");
$response->assertStatus(Response::HTTP_OK);
$response->assertJsonCount(2, 'data');

foreach ($allocations as $allocation) {
$response->assertJsonFragment(['id' => $allocation->id, 'port' => $allocation->port]);
}
}

public function test_create_allocations_for_single_ports_and_ranges(): void
{
$this->postJson("/api/application/nodes/{$this->node->id}/allocations", [
'ip' => '10.0.0.1',
'ports' => ['25565', '25570-25572'],
])->assertStatus(Response::HTTP_NO_CONTENT);

$this->assertEquals(4, Allocation::query()->where('node_id', $this->node->id)->count());
$this->assertDatabaseHas('allocations', ['node_id' => $this->node->id, 'ip' => '10.0.0.1', 'port' => 25565]);
$this->assertDatabaseHas('allocations', ['node_id' => $this->node->id, 'ip' => '10.0.0.1', 'port' => 25572]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

public function test_create_allocations_requires_ports(): void
{
$this->postJson("/api/application/nodes/{$this->node->id}/allocations", [
'ip' => '10.0.0.1',
])->assertUnprocessable();
}

public function test_delete_unassigned_allocation(): void
{
$allocation = Allocation::factory()->create(['node_id' => $this->node->id, 'server_id' => null]);

$this->deleteJson("/api/application/nodes/{$this->node->id}/allocations/{$allocation->id}")
->assertStatus(Response::HTTP_NO_CONTENT);

$this->assertDatabaseMissing('allocations', ['id' => $allocation->id]);
}

public function test_assigned_allocation_cannot_be_deleted(): void
{
$server = $this->createServerModel(['node_id' => $this->node->id]);

$this->deleteJson("/api/application/nodes/{$this->node->id}/allocations/{$server->allocation_id}")
->assertStatus(Response::HTTP_BAD_REQUEST);

$this->assertDatabaseHas('allocations', ['id' => $server->allocation_id]);
}

public function test_error_returned_if_no_permission(): void
{
$this->createNewDefaultApiKey($this->getApiUser(), [Allocation::RESOURCE_NAME => AdminAcl::NONE]);

$response = $this->getJson("/api/application/nodes/{$this->node->id}/allocations");
$this->assertAccessDeniedJson($response);
}
}
148 changes: 148 additions & 0 deletions tests/Integration/Api/Application/DatabaseHostControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

namespace App\Tests\Integration\Api\Application;

use App\Data\Api\Application\DatabaseHostData;
use App\Models\DatabaseHost;
use App\Services\Acl\Api\AdminAcl;
use Exception;
use Illuminate\Database\Connection;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Mockery;
use PDO;
use PDOException;

class DatabaseHostControllerTest extends ApplicationApiIntegrationTestCase
{
/**
* The host services confirm access via DatabaseHost->buildConnection()->getPdo(),
* so fake the remote connection while leaving the panel's own connection real.
*/
private function fakeRemoteDatabaseConnection(?Exception $exception = null): void
{
$connection = Mockery::mock(Connection::class);

if ($exception) {
$connection->shouldReceive('getPdo')->andThrow($exception);
} else {
$connection->shouldReceive('getPdo')->andReturn(Mockery::mock(PDO::class));
}

$manager = Mockery::mock(app('db'));
$manager->shouldReceive('build')->andReturn($connection);
DB::swap($manager);
$this->app->instance('db', $manager);
}

public function test_list_all_database_hosts(): void
{
$host = DatabaseHost::factory()->create();

$response = $this->getJson('/api/application/database-hosts');
$response->assertStatus(Response::HTTP_OK);
$response->assertJsonCount(1, 'data');
$response->assertJsonFragment(['id' => $host->id]);
}

public function test_return_single_database_host(): void
{
$host = DatabaseHost::factory()->create();

$response = $this->getJson('/api/application/database-hosts/' . $host->id);
$response->assertStatus(Response::HTTP_OK);
$response->assertJson([
'object' => 'database_host',
'attributes' => $this->getExpectedData(DatabaseHostData::class, $host),
], true);
}

public function test_create_database_host(): void
{
$this->fakeRemoteDatabaseConnection();

$response = $this->postJson('/api/application/database-hosts', [
'name' => 'api-db-host',
'host' => '127.0.0.1',
'port' => 3306,
'username' => 'pelicanuser',
'password' => 'secret1234',
]);

$response->assertStatus(Response::HTTP_CREATED);
$this->assertDatabaseHas('database_hosts', ['name' => 'api-db-host', 'host' => '127.0.0.1']);
}

public function test_database_host_is_not_saved_when_connection_fails(): void
{
$this->fakeRemoteDatabaseConnection(new PDOException('Connection refused'));

$this->postJson('/api/application/database-hosts', [
'name' => 'unreachable-host',
'host' => '10.0.0.99',
'port' => 3306,
'username' => 'pelicanuser',
'password' => 'secret1234',
])->assertServerError();

$this->assertDatabaseMissing('database_hosts', ['name' => 'unreachable-host']);
}

public function test_create_database_host_requires_host(): void
{
$this->postJson('/api/application/database-hosts', [
'name' => 'api-db-host',
'port' => 3306,
'username' => 'pelicanuser',
])->assertUnprocessable();
}

public function test_update_database_host(): void
{
$this->fakeRemoteDatabaseConnection();

$host = DatabaseHost::factory()->create();

$response = $this->patchJson('/api/application/database-hosts/' . $host->id, [
'name' => 'renamed-db-host',
'host' => $host->host,
'port' => $host->port,
'username' => $host->username,
]);

$response->assertStatus(Response::HTTP_OK);
$this->assertDatabaseHas('database_hosts', ['id' => $host->id, 'name' => 'renamed-db-host']);
}

public function test_delete_database_host(): void
{
$host = DatabaseHost::factory()->create();

$this->deleteJson('/api/application/database-hosts/' . $host->id)
->assertStatus(Response::HTTP_NO_CONTENT);

$this->assertDatabaseMissing('database_hosts', ['id' => $host->id]);
}

public function test_error_returned_if_no_permission(): void
{
$host = DatabaseHost::factory()->create();
$this->createNewDefaultApiKey($this->getApiUser(), [DatabaseHost::RESOURCE_NAME => AdminAcl::NONE]);

$response = $this->getJson('/api/application/database-hosts/' . $host->id);
$this->assertAccessDeniedJson($response);
}

public function test_api_key_without_write_permissions_cannot_create(): void
{
$this->createNewDefaultApiKey($this->getApiUser(), [DatabaseHost::RESOURCE_NAME => AdminAcl::READ]);

$response = $this->postJson('/api/application/database-hosts', [
'name' => 'api-db-host',
'host' => '127.0.0.1',
'port' => 3306,
'username' => 'pelicanuser',
]);
$this->assertAccessDeniedJson($response);
}
}
Loading
Loading