diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php index 4699dc998a1..f632385705f 100644 --- a/app/Http/Controllers/Admin/LocationController.php +++ b/app/Http/Controllers/Admin/LocationController.php @@ -18,6 +18,7 @@ public function index(Request $request) { $locations = QueryBuilder::for(Location::query()) ->withCount(['nodes', 'servers']) + ->defaultSort('-id') // @phpstan-ignore-next-line ->allowedFilters( ['short_code', AllowedFilter::custom('*', new FiltersLocationWildcard())], diff --git a/app/Http/Controllers/Admin/TokenController.php b/app/Http/Controllers/Admin/TokenController.php index 32bcd8a860d..14e83dee5ab 100644 --- a/app/Http/Controllers/Admin/TokenController.php +++ b/app/Http/Controllers/Admin/TokenController.php @@ -17,6 +17,7 @@ public function index(Request $request) { $tokens = QueryBuilder::for(PersonalAccessToken::query()) ->with('tokenable') + ->defaultSort('-id') ->where('personal_access_tokens.type', ApiKeyType::APPLICATION->value) ->paginate(min($request->query('per_page', 50), 100))->appends( $request->query(), diff --git a/database/factories/ServerFactory.php b/database/factories/ServerFactory.php index 72e7a6cf07d..f858c18d35a 100644 --- a/database/factories/ServerFactory.php +++ b/database/factories/ServerFactory.php @@ -2,9 +2,7 @@ namespace Database\Factories; -use Convoy\Models\Node; use Convoy\Models\Server; -use Convoy\Models\User; use Convoy\Services\Servers\ServerCreationService; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\App; @@ -32,8 +30,11 @@ public function definition(): array 'name' => $this->faker->word(), 'vmid' => rand(100, 5000), 'cpu' => 2, - 'memory' => 17179869184, - 'disk' => 17179869184, + 'memory' => 2048 * 1024 * 1024, + 'disk' => 20 * 1024 * 1024 * 1024, + 'backup_limit' => 16, + 'snapshot_limit' => 16, + 'bandwidth_limit' => 100 * 1024 * 1024 * 1024, ]; } } diff --git a/tests/Feature/Controllers/Admin/LocationControllerTest.php b/tests/Feature/Controllers/Admin/LocationControllerTest.php new file mode 100644 index 00000000000..c6291f2c3a0 --- /dev/null +++ b/tests/Feature/Controllers/Admin/LocationControllerTest.php @@ -0,0 +1,56 @@ +create([ + 'root_admin' => true, + ]); + + $response = $this->actingAs($user)->getJson('/api/admin/locations'); + + $response->assertOk(); +}); + +it('can create a location', function () { + $user = User::factory()->create([ + 'root_admin' => true, + ]); + + $response = $this->actingAs($user)->postJson('/api/admin/locations', [ + 'name' => 'Test Location', + 'short_code' => 'test', + 'description' => 'This is a test location.', + ]); + + $response->assertOk(); +}); + +it('can update a location', function () { + $user = User::factory()->create([ + 'root_admin' => true, + ]); + + $location = Location::factory()->create(); + + $response = $this->actingAs($user)->putJson("/api/admin/locations/{$location->id}", [ + 'name' => 'Test Location', + 'short_code' => 'test', + 'description' => 'This is a test location.', + ]); + + $response->assertOk(); +}); + +it('can delete a location', function () { + $user = User::factory()->create([ + 'root_admin' => true, + ]); + + $location = Location::factory()->create(); + + $response = $this->actingAs($user)->deleteJson("/api/admin/locations/{$location->id}"); + + $response->assertNoContent(); +}); \ No newline at end of file diff --git a/tests/Feature/Controllers/Admin/Nodes/NodeControllerTest.php b/tests/Feature/Controllers/Admin/Nodes/NodeControllerTest.php new file mode 100644 index 00000000000..72f4dc1b257 --- /dev/null +++ b/tests/Feature/Controllers/Admin/Nodes/NodeControllerTest.php @@ -0,0 +1,149 @@ +user = User::factory()->create([ + 'root_admin' => true, + ]); + $this->location = Location::factory()->create(); + $this->node = Node::factory()->for($this->location)->create(); +}); + +it('can fetch nodes', function () { + $response = $this->actingAs($this->user)->getJson('/api/admin/nodes'); + + $response->assertOk(); +}); + +it('can fetch a node', function () { + $response = $this->actingAs($this->user)->getJson("/api/admin/nodes/{$this->node->id}"); + + $response->assertOk(); +}); + +it('can create a node', function () { + $response = $this->actingAs($this->user)->postJson('/api/admin/nodes', [ + 'location_id' => $this->location->id, + 'name' => 'Test Node', + 'cluster' => 'proxmox', + 'fqdn' => 'example.com', + 'token_id' => 'test-token', + 'secret' => 'test-secret', + 'port' => 8006, + 'memory' => 64 * 1024 * 1024 * 1024, // 64GB, + 'memory_overallocate' => 0, + 'disk' => 512 * 1024 * 1024 * 1024, // 512GB, + 'disk_overallocate' => 0, + 'vm_storage' => 'local-lvm', + 'backup_storage' => 'local-lvm', + 'iso_storage' => 'local-lvm', + 'network' => 'vmbr0', + ]); + + $response->assertOk(); +}); + +it('can update a node', function () { + $response = $this->actingAs($this->user)->putJson("/api/admin/nodes/{$this->node->id}", [ + 'location_id' => $this->location->id, + 'name' => 'Test Node', + 'cluster' => 'proxmox', + 'fqdn' => 'example.com', + 'token_id' => 'test-token', + 'secret' => 'test-secret', + 'port' => 8006, + 'memory' => 64 * 1024 * 1024 * 1024, // 64GB, + 'memory_overallocate' => 0, + 'disk' => 512 * 1024 * 1024 * 1024, // 512GB, + 'disk_overallocate' => 0, + 'vm_storage' => 'local-lvm', + 'backup_storage' => 'local-lvm', + 'iso_storage' => 'local-lvm', + 'network' => 'vmbr0', + ]); + + $response->assertOk(); +}); + +it("can't downsize without over-allocating", function () { + $node = Node::factory()->for($this->location)->create([ + 'memory' => 64 * 1024 * 1024 * 1024, // 64GB, + 'disk' => 512 * 1024 * 1024 * 1024, // 512GB, + ]); + + Server::factory()->for($node)->for($this->user)->create([ + 'memory' => 32 * 1024 * 1024 * 1024, // 32GB, + 'disk' => 256 * 1024 * 1024 * 1024, // 256GB, + ]); + + $response = $this->actingAs($this->user)->putJson("/api/admin/nodes/{$node->id}", [ + 'location_id' => $this->location->id, + 'name' => 'Test Node', + 'cluster' => 'proxmox', + 'fqdn' => 'example.com', + 'token_id' => 'test-token', + 'secret' => 'test-secret', + 'port' => 8006, + 'memory' => 16 * 1024 * 1024 * 1024, // 16GB, + 'memory_overallocate' => 0, + 'disk' => 128 * 1024 * 1024 * 1024, // 128GB, + 'disk_overallocate' => 0, + 'vm_storage' => 'local-lvm', + 'backup_storage' => 'local-lvm', + 'iso_storage' => 'local-lvm', + 'network' => 'vmbr0', + ]); + + $response->assertStatus(422); +}); + +it('can update node without false positive overallocation', function () { + $node = Node::factory()->for($this->location)->create([ + 'memory' => 64 * 1024 * 1024 * 1024, // 64GB, + 'disk' => 512 * 1024 * 1024 * 1024, // 512GB, + ]); + + Server::factory()->for($node)->for($this->user)->create([ + 'memory' => 64 * 1024 * 1024 * 1024, // 64GB, + 'disk' => 256 * 1024 * 1024 * 1024, // 256GB, + ]); + + $response = $this->actingAs($this->user)->putJson("/api/admin/nodes/{$node->id}", [ + 'location_id' => $this->location->id, + 'name' => 'New name', + 'cluster' => 'proxmox', + 'fqdn' => 'example.com', + 'token_id' => 'test-token', + 'secret' => 'test-secret', + 'port' => 8006, + 'memory' => 64 * 1024 * 1024 * 1024, // 64GB, + 'memory_overallocate' => 0, + 'disk' => 512 * 1024 * 1024 * 1024, // 512GB, + 'disk_overallocate' => 0, + 'vm_storage' => 'local-lvm', + 'backup_storage' => 'local-lvm', + 'iso_storage' => 'local-lvm', + 'network' => 'vmbr0', + ]); + + $response->assertOk(); +}); + +it('can delete a node', function () { + $response = $this->actingAs($this->user)->deleteJson("/api/admin/nodes/{$this->node->id}"); + + $response->assertNoContent(); +}); + +it("can't delete a node with servers", function () { + Server::factory()->for($this->node)->for($this->user)->create(); + + $response = $this->actingAs($this->user)->deleteJson("/api/admin/nodes/{$this->node->id}"); + + $response->assertForbidden(); +}); diff --git a/tests/Feature/Controllers/Client/Servers/BackupControllerTest.php b/tests/Feature/Controllers/Client/Servers/BackupControllerTest.php index 646a55d0a63..4b6398548a3 100644 --- a/tests/Feature/Controllers/Client/Servers/BackupControllerTest.php +++ b/tests/Feature/Controllers/Client/Servers/BackupControllerTest.php @@ -7,88 +7,22 @@ use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Queue; -it('can create backups', function () { - Http::fake([ - '*' => Http::response(['data' => 'upid'], 200), - ]); - - [$user, $_, $_, $server] = createServerModel(); - - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/backups", [ - 'name' => 'Test Backup', - 'mode' => 'snapshot', - 'compression_type' => 'none', - 'is_locked' => false, - ]); - - $response->assertOk() - ->assertJsonPath('data.name', 'Test Backup') - ->assertJsonPath('data.is_locked', 0); - - Queue::assertPushed(MonitorBackupJob::class); -}); - -it('can restore backups', function () { - Http::fake([ - '*/status/current' => Http::response( - file_get_contents( - base_path('tests/Fixtures/Repositories/Server/GetStoppedServerStatusData.json'), - ), 200, - ), - '*' => Http::response(['data' => 'dummy-upid'], 200), - - ]); - - [$user, $_, $_, $server] = createServerModel(); - - $backup = Backup::factory()->create([ - 'is_successful' => true, - 'is_locked' => false, - 'server_id' => $server->id, - ]); - - $response = $this->actingAs($user)->postJson( - "/api/client/servers/{$server->uuid}/backups/{$backup->uuid}/restore", - ); - - $response->assertNoContent(); - - Queue::assertPushed(MonitorBackupRestorationJob::class); -}); - -it('can delete backups', function () { - Http::fake([ - '*' => Http::response(['data' => 'dummy-upid'], 200), - ]); - - [$user, $_, $_, $server] = createServerModel(); - - $backup = Backup::factory()->create([ - 'is_successful' => true, - 'is_locked' => false, - 'server_id' => $server->id, - ]); - - $response = $this->actingAs($user)->deleteJson( - "/api/client/servers/{$server->uuid}/backups/{$backup->uuid}", - ); - - $response->assertNoContent(); -}); - -describe('admin', function () { - it('can create backups', function () { +function testCreateBackup(bool $useSecondUser = false, bool $secondUserIsAdmin = false): Closure +{ + return function () use ($useSecondUser, $secondUserIsAdmin) { Http::fake([ '*' => Http::response(['data' => 'upid'], 200), ]); - [$_, $_, $_, $server] = createServerModel(); + [$user, $_, $_, $server] = createServerModel(); - $admin = User::factory()->create([ - 'root_admin' => true, - ]); + if ($useSecondUser) { + $user = User::factory()->create([ + 'root_admin' => $secondUserIsAdmin, + ]); + } - $response = $this->actingAs($admin)->postJson( + $response = $this->actingAs($user)->postJson( "/api/client/servers/{$server->uuid}/backups", [ 'name' => 'Test Backup', 'mode' => 'snapshot', @@ -97,14 +31,23 @@ ], ); + if ($useSecondUser && !$secondUserIsAdmin) { + $response->assertNotFound(); + + return; + } + $response->assertOk() ->assertJsonPath('data.name', 'Test Backup') ->assertJsonPath('data.is_locked', 0); Queue::assertPushed(MonitorBackupJob::class); - }); + }; +} - it('can restore backups', function () { +function testRestoreBackups(bool $useSecondUser = false, bool $secondUserIsAdmin = false): Closure +{ + return function () use ($useSecondUser, $secondUserIsAdmin) { Http::fake([ '*/status/current' => Http::response( file_get_contents( @@ -115,11 +58,13 @@ ]); - [$_, $_, $_, $server] = createServerModel(); + [$user, $_, $_, $server] = createServerModel(); - $admin = User::factory()->create([ - 'root_admin' => true, - ]); + if ($useSecondUser) { + $user = User::factory()->create([ + 'root_admin' => $secondUserIsAdmin, + ]); + } $backup = Backup::factory()->create([ 'is_successful' => true, @@ -127,25 +72,36 @@ 'server_id' => $server->id, ]); - $response = $this->actingAs($admin)->postJson( + $response = $this->actingAs($user)->postJson( "/api/client/servers/{$server->uuid}/backups/{$backup->uuid}/restore", ); + if ($useSecondUser && !$secondUserIsAdmin) { + $response->assertNotFound(); + + return; + } + $response->assertNoContent(); Queue::assertPushed(MonitorBackupRestorationJob::class); - }); + }; +} - it('can delete backups', function () { +function testDeleteBackups(bool $useSecondUser = false, bool $secondUserIsAdmin = false): Closure +{ + return function () use ($useSecondUser, $secondUserIsAdmin) { Http::fake([ '*' => Http::response(['data' => 'dummy-upid'], 200), ]); - [$_, $_, $_, $server] = createServerModel(); + [$user, $_, $_, $server] = createServerModel(); - $admin = User::factory()->create([ - 'root_admin' => true, - ]); + if ($useSecondUser) { + $user = User::factory()->create([ + 'root_admin' => $secondUserIsAdmin, + ]); + } $backup = Backup::factory()->create([ 'is_successful' => true, @@ -153,11 +109,38 @@ 'server_id' => $server->id, ]); - $response = $this->actingAs($admin)->deleteJson( + $response = $this->actingAs($user)->deleteJson( "/api/client/servers/{$server->uuid}/backups/{$backup->uuid}", ); + if ($useSecondUser && !$secondUserIsAdmin) { + $response->assertNotFound(); + + return; + } + $response->assertNoContent(); - }); + }; +} + +it('can create backups', testCreateBackup()); + +it('can restore backups', testRestoreBackups()); + +it('can delete backups', testDeleteBackups()); + +describe('admin', function () { + it('can create backups', testCreateBackup(true, true)); + + it('can restore backups', testRestoreBackups(true, true)); + + it('can delete backups', testDeleteBackups(true, true)); }); +describe('unauthorized users', function () { + it("can't create backups", testCreateBackup(true)); + + it("can't restore backups", testRestoreBackups(true)); + + it("can't delete backups", testDeleteBackups(true)); +}); \ No newline at end of file diff --git a/tests/Feature/Controllers/Client/Servers/SettingsControllerTest.php b/tests/Feature/Controllers/Client/Servers/SettingsControllerTest.php index b1a209aa5ca..6de633b5f0f 100644 --- a/tests/Feature/Controllers/Client/Servers/SettingsControllerTest.php +++ b/tests/Feature/Controllers/Client/Servers/SettingsControllerTest.php @@ -3,8 +3,6 @@ use Convoy\Models\ISO; use Illuminate\Support\Facades\Http; -beforeEach(fn () => Http::preventStrayRequests()); - it('can rename servers', function () { Http::fake([ '*' => Http::response(['data' => 'dummy-upid'], 200), @@ -12,66 +10,90 @@ [$user, $_, $_, $server] = createServerModel(); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/rename", [ + $response = $this->actingAs($user)->postJson( + "/api/client/servers/{$server->uuid}/settings/rename", [ 'name' => 'advinservers is king', 'hostname' => 'advinservers.com', - ]); + ], + ); $response->assertOk() - ->assertJsonPath('data.name', 'advinservers is king') - ->assertJsonPath('data.hostname', 'advinservers.com'); + ->assertJsonPath('data.name', 'advinservers is king') + ->assertJsonPath('data.hostname', 'advinservers.com'); }); it('can change nameservers', function () { Http::fake([ - '*/config' => Http::response(file_get_contents(base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json')), 200), + '*/config' => Http::response( + file_get_contents( + base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json'), + ), 200, + ), '*' => Http::response(['data' => 'dummy-upid'], 200), ]); [$user, $_, $_, $server] = createServerModel(); - $response = $this->actingAs($user)->putJson("/api/client/servers/{$server->uuid}/settings/network", [ + $response = $this->actingAs($user)->putJson( + "/api/client/servers/{$server->uuid}/settings/network", [ 'nameservers' => [ '1.1.1.1', '1.0.0.1', ], - ]); + ], + ); $response->assertOk(); }); it('can fetch sshkeys', function () { Http::fake([ - '*/config' => Http::response(file_get_contents(base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json')), 200), + '*/config' => Http::response( + file_get_contents( + base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json'), + ), 200, + ), '*' => Http::response(['data' => 'dummy-upid'], 200), ]); [$user, $_, $_, $server] = createServerModel(); - $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/settings/auth"); + $response = $this->actingAs($user)->getJson( + "/api/client/servers/{$server->uuid}/settings/auth", + ); $response->assertOk(); }); it('can change server passwords', function () { Http::fake([ - '*/config' => Http::response(file_get_contents(base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json')), 200), + '*/config' => Http::response( + file_get_contents( + base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json'), + ), 200, + ), '*' => Http::response(['data' => 'dummy-upid'], 200), ]); [$user, $_, $_, $server] = createServerModel(); - $response = $this->actingAs($user)->putJson("/api/client/servers/{$server->uuid}/settings/auth", [ + $response = $this->actingAs($user)->putJson( + "/api/client/servers/{$server->uuid}/settings/auth", [ 'type' => 'password', 'password' => 'Advinservers is king!123', - ]); + ], + ); $response->assertNoContent(); }); it('can fetch available ISOs', function () { Http::fake([ - '*/config' => Http::response(file_get_contents(base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json')), 200), + '*/config' => Http::response( + file_get_contents( + base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json'), + ), 200, + ), '*' => Http::response(['data' => 'dummy-upid'], 200), ]); @@ -82,14 +104,20 @@ 'hidden' => false, ]); - $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/settings/hardware/isos"); + $response = $this->actingAs($user)->getJson( + "/api/client/servers/{$server->uuid}/settings/hardware/isos", + ); $response->assertOk(); }); it('can mount visible ISOs', function () { Http::fake([ - '*/config' => Http::response(file_get_contents(base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json')), 200), + '*/config' => Http::response( + file_get_contents( + base_path('tests/Fixtures/Repositories/Server/GetServerConfigData.json'), + ), 200, + ), '*' => Http::response(['data' => 'dummy-upid'], 200), ]); @@ -100,7 +128,9 @@ 'hidden' => false, ]); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/hardware/isos/{$iso->uuid}/mount"); + $response = $this->actingAs($user)->postJson( + "/api/client/servers/{$server->uuid}/settings/hardware/isos/{$iso->uuid}/mount", + ); $response->assertNoContent(); }); @@ -113,7 +143,9 @@ 'hidden' => true, ]); - $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/hardware/isos/{$iso->uuid}/mount"); + $response = $this->actingAs($user)->postJson( + "/api/client/servers/{$server->uuid}/settings/hardware/isos/{$iso->uuid}/mount", + ); $response->assertStatus(403); }); diff --git a/tests/Unit/Services/Nodes/ServerRateLimitsSyncServiceTest.php b/tests/Unit/Services/Nodes/ServerRateLimitsSyncServiceTest.php index d3e67b99f74..5d965aef460 100644 --- a/tests/Unit/Services/Nodes/ServerRateLimitsSyncServiceTest.php +++ b/tests/Unit/Services/Nodes/ServerRateLimitsSyncServiceTest.php @@ -4,7 +4,7 @@ use Illuminate\Http\Client\Request; use Illuminate\Support\Facades\Http; -rit('can rate limit servers if over limit', function () { +it('can rate limit servers if over limit', function () { Http::fake([ '*' => Http::response(['data' => 'dummy-upid'], 200), ]);