mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-29 01:54:37 +08:00
477 lines
23 KiB
PHP
477 lines
23 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Server\Index as ServerIndex;
|
|
use App\Models\Application;
|
|
use App\Models\Environment;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\Project;
|
|
use App\Models\ResourceLink;
|
|
use App\Models\Server;
|
|
use App\Models\StandaloneDocker;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use App\Services\ServerCanvas;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Cookie;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
config([
|
|
'cache.default' => 'array',
|
|
'session.driver' => 'array',
|
|
'queue.default' => 'sync',
|
|
'app.maintenance.driver' => 'file',
|
|
]);
|
|
InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0], ['is_api_enabled' => true]));
|
|
Server::flushIdentityMap();
|
|
|
|
$this->team = Team::factory()->create(['show_boarding' => false]);
|
|
$this->admin = User::factory()->create();
|
|
$this->team->members()->attach($this->admin->id, ['role' => 'admin']);
|
|
|
|
$this->web = Server::factory()->create(['team_id' => $this->team->id, 'name' => 'web-server', 'ip' => '203.0.113.20']);
|
|
$this->db = Server::factory()->create(['team_id' => $this->team->id, 'name' => 'db-server']);
|
|
$this->project = Project::factory()->create(['team_id' => $this->team->id, 'name' => 'Shop']);
|
|
$this->environment = Environment::factory()->create(['project_id' => $this->project->id, 'name' => 'staging']);
|
|
});
|
|
|
|
function serverCanvasApp(Environment $environment, Server $server, string $name): Application
|
|
{
|
|
$destination = StandaloneDocker::query()->where('server_id', $server->id)->firstOrFail();
|
|
|
|
return Application::factory()->create([
|
|
'name' => $name,
|
|
'environment_id' => $environment->id,
|
|
'destination_id' => $destination->id,
|
|
'destination_type' => $destination->getMorphClass(),
|
|
]);
|
|
}
|
|
|
|
function serverCanvasSession(object $test, User $user, Team $team): object
|
|
{
|
|
return $test->actingAs($user)->withSession(['currentTeam' => $team])
|
|
->withHeader('X-Canvas-Team', (string) $team->id);
|
|
}
|
|
|
|
test('the servers canvas shows every server of the team with its resources and links', function () {
|
|
$app = serverCanvasApp($this->environment, $this->web, 'storefront');
|
|
$api = serverCanvasApp($this->environment, $this->db, 'orders-api');
|
|
$link = ResourceLink::create([
|
|
'environment_id' => $this->environment->id,
|
|
'source_type' => $app->getMorphClass(), 'source_id' => $app->id,
|
|
'target_type' => $api->getMorphClass(), 'target_id' => $api->id,
|
|
]);
|
|
|
|
$response = serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson('/canvas/api/servers/canvas')
|
|
->assertOk()
|
|
->assertJsonPath('title', 'Servers')
|
|
->assertJsonPath('title_link', null)
|
|
->assertJsonPath('can_edit', true)
|
|
->assertJsonPath('layout', 'server')
|
|
->assertJsonPath('positions', [])
|
|
->assertJsonPath('links.0.id', $link->uuid);
|
|
|
|
$resources = collect($response->json('nodes'))->where('type', '!=', 'empty');
|
|
|
|
// The project's empty production environment shows as a placeholder card on the server of its other environments.
|
|
expect(collect($response->json('zones'))->pluck('name')->all())->toBe(['db-server', 'web-server'])
|
|
->and(collect($response->json('nodes'))->mapWithKeys(fn ($node) => [$node['name'] => [$node['zone'], $node['context']]])->all())->toBe([
|
|
'storefront' => [$this->web->uuid, 'Shop / staging'],
|
|
'orders-api' => [$this->db->uuid, 'Shop / staging'],
|
|
'No resources yet' => [$this->web->uuid, 'Shop / production'],
|
|
])
|
|
->and($response->json('nodes.0.project'))->toBe(['uuid' => $this->project->uuid, 'name' => 'Shop'])
|
|
->and($response->json('nodes.0.environment'))->toBe(['uuid' => $this->environment->uuid, 'name' => 'staging'])
|
|
->and($resources->pluck('context_link')->unique()->all())->toBe([
|
|
route('project.resource.index', ['project_uuid' => $this->project->uuid, 'environment_uuid' => $this->environment->uuid]),
|
|
])
|
|
->and(collect($response->json('zones'))->pluck('link')->all())->toBe([
|
|
route('server.show', ['server_uuid' => $this->db->uuid]),
|
|
route('server.show', ['server_uuid' => $this->web->uuid]),
|
|
]);
|
|
});
|
|
|
|
test('the servers canvas puts empty environments of a project without resources on the first server', function () {
|
|
$bare = Project::factory()->create(['team_id' => $this->team->id, 'name' => 'Bare']);
|
|
|
|
$response = serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson('/canvas/api/servers/canvas')
|
|
->assertOk();
|
|
|
|
expect(collect($response->json('zones'))->pluck('name')->all())->toBe(['db-server', 'web-server'])
|
|
->and(collect($response->json('nodes'))->where('project.uuid', $bare->uuid)->pluck('zone')->all())->toBe([$this->db->uuid]);
|
|
});
|
|
|
|
test('the servers canvas shows a server without resources as a placeholder card in its zone', function () {
|
|
serverCanvasApp($this->environment, $this->web, 'storefront');
|
|
Environment::query()->where('project_id', $this->project->id)->where('name', '!=', 'staging')->delete();
|
|
|
|
$response = serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson('/canvas/api/servers/canvas')
|
|
->assertOk();
|
|
|
|
$placeholder = collect($response->json('nodes'))->firstWhere('zone', $this->db->uuid);
|
|
|
|
expect($placeholder)->toMatchArray([
|
|
'id' => 'empty-server:'.$this->db->uuid,
|
|
'type' => 'empty',
|
|
'name' => 'No resources yet',
|
|
'link' => route('server.show', ['server_uuid' => $this->db->uuid]),
|
|
])->and(collect($response->json('nodes'))->where('zone', $this->web->uuid)->pluck('name')->all())->toBe(['storefront']);
|
|
});
|
|
|
|
test('one server canvas shows only that server, and links that leave it are hidden', function () {
|
|
$app = serverCanvasApp($this->environment, $this->web, 'storefront');
|
|
$api = serverCanvasApp($this->environment, $this->db, 'orders-api');
|
|
ResourceLink::create([
|
|
'environment_id' => $this->environment->id,
|
|
'source_type' => $app->getMorphClass(), 'source_id' => $app->id,
|
|
'target_type' => $api->getMorphClass(), 'target_id' => $api->id,
|
|
]);
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson("/canvas/api/servers/{$this->web->uuid}/canvas")
|
|
->assertOk()
|
|
->assertJsonPath('title', 'web-server')
|
|
->assertJsonPath('title_link', route('server.show', ['server_uuid' => $this->web->uuid]))
|
|
->assertJsonCount(1, 'zones')
|
|
->assertJsonPath('nodes.0.name', 'storefront')
|
|
->assertJsonCount(1, 'nodes')
|
|
->assertJsonPath('links', []);
|
|
});
|
|
|
|
test('the server canvas sends the card positions saved on the environment canvas with each card', function () {
|
|
$app = serverCanvasApp($this->environment, $this->web, 'storefront');
|
|
$other = serverCanvasApp($this->environment, $this->web, 'checkout');
|
|
$this->environment->forceFill(['canvas_positions' => [$app->uuid => ['x' => 120, 'y' => 40]]])->save();
|
|
|
|
$nodes = collect(serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson('/canvas/api/servers/canvas')
|
|
->assertOk()
|
|
->assertJsonPath('can_edit', true)
|
|
->assertJsonPath('can_move', false)
|
|
->assertJsonPath('positions', [])
|
|
->json('nodes'))->keyBy('id');
|
|
|
|
expect($nodes[$app->uuid]['saved'])->toBe(['x' => 120, 'y' => 40])
|
|
->and($nodes[$other->uuid]['saved'])->toBeNull();
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson("/canvas/api/servers/{$this->web->uuid}/canvas")
|
|
->assertOk()
|
|
->assertJsonPath('can_move', false)
|
|
->assertJsonPath('positions', []);
|
|
});
|
|
|
|
test('members see the server canvas without server IPs and cannot edit it', function () {
|
|
$member = User::factory()->create();
|
|
$this->team->members()->attach($member->id, ['role' => 'member']);
|
|
serverCanvasApp($this->environment, $this->web, 'storefront');
|
|
|
|
serverCanvasSession($this, $member, $this->team)
|
|
->getJson("/canvas/api/servers/{$this->web->uuid}/canvas")
|
|
->assertOk()
|
|
->assertJsonPath('can_edit', false)
|
|
->assertJsonPath('zones.0.ip', null);
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson("/canvas/api/servers/{$this->web->uuid}/canvas")
|
|
->assertJsonPath('zones.0.ip', '203.0.113.20');
|
|
});
|
|
|
|
test('the server canvas never shows another team servers or resources', function () {
|
|
$otherTeam = Team::factory()->create(['show_boarding' => false]);
|
|
$outsider = User::factory()->create();
|
|
$otherTeam->members()->attach($outsider->id, ['role' => 'owner']);
|
|
$this->team->members()->attach($outsider->id, ['role' => 'member']);
|
|
serverCanvasApp($this->environment, $this->web, 'storefront');
|
|
|
|
// Session switched to the other team: this team's server is out of reach.
|
|
serverCanvasSession($this, $outsider, $otherTeam)
|
|
->getJson("/canvas/api/servers/{$this->web->uuid}/canvas")
|
|
->assertNotFound();
|
|
$all = serverCanvasSession($this, $outsider, $otherTeam)
|
|
->getJson('/canvas/api/servers/canvas')
|
|
->assertOk();
|
|
expect($all->json('zones'))->toBe([])->and($all->json('nodes'))->toBe([]);
|
|
|
|
serverCanvasSession($this, $outsider, $otherTeam)
|
|
->get(route('server.canvas', ['server_uuid' => $this->web->uuid]))
|
|
->assertNotFound();
|
|
});
|
|
|
|
test('the canvas pages open for team members', function () {
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withoutVite()
|
|
->get(route('server.canvas.index'))
|
|
->assertOk()
|
|
->assertSee('data-endpoint="/servers/canvas"', false);
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withoutVite()
|
|
->get(route('server.canvas', ['server_uuid' => $this->web->uuid]))
|
|
->assertOk()
|
|
->assertSee('data-endpoint="/servers/'.$this->web->uuid.'/canvas"', false);
|
|
});
|
|
|
|
test('the servers page reopens the canvas when the user left it on the canvas view', function () {
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->get(route('server.canvas.index'))
|
|
->assertOk()
|
|
->assertCookie("coolify_{$this->admin->id}_servers_view", 'canvas');
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
|
|
->get(route('server.index'))
|
|
->assertRedirect(route('server.canvas.index'));
|
|
|
|
// The table / grid links on the canvas pick the list view again.
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
|
|
->get(route('server.index', ['view' => 'grid']))
|
|
->assertOk()
|
|
->assertCookie("coolify_{$this->admin->id}_servers_view", 'grid');
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withCookie("coolify_{$this->admin->id}_servers_view", 'grid')
|
|
->get(route('server.index'))
|
|
->assertOk();
|
|
});
|
|
|
|
test('the environment page reopens the canvas when the user left it on the canvas view', function () {
|
|
$parameters = ['project_uuid' => $this->project->uuid, 'environment_uuid' => $this->environment->uuid];
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->get(route('project.environment.canvas', $parameters))
|
|
->assertOk()
|
|
->assertCookie("coolify_{$this->admin->id}_environment_view", 'canvas');
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withCookie("coolify_{$this->admin->id}_environment_view", 'canvas')
|
|
->get(route('project.resource.index', $parameters))
|
|
->assertRedirect(route('project.environment.canvas', $parameters));
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withCookie("coolify_{$this->admin->id}_environment_view", 'canvas')
|
|
->get(route('project.resource.index', [...$parameters, 'view' => 'table']))
|
|
->assertOk()
|
|
->assertCookie("coolify_{$this->admin->id}_environment_view", 'table');
|
|
});
|
|
|
|
test('the server resources page reopens the one server canvas when the user left it on the canvas view', function () {
|
|
$parameters = ['server_uuid' => $this->web->uuid];
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->get(route('server.canvas', $parameters))
|
|
->assertOk()
|
|
->assertCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas');
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas')
|
|
->get(route('server.resources', $parameters))
|
|
->assertRedirect(route('server.canvas', $parameters));
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withCookie("coolify_{$this->admin->id}_server_resources_view", 'canvas')
|
|
->get(route('server.resources', [...$parameters, 'view' => 'list']))
|
|
->assertOk()
|
|
->assertCookie("coolify_{$this->admin->id}_server_resources_view", 'list');
|
|
});
|
|
|
|
test('the canvas API works with the browser session when the instance API is disabled', function () {
|
|
InstanceSettings::query()->whereKey(0)->update(['is_api_enabled' => false]);
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson('/canvas/api/servers/canvas')
|
|
->assertOk()
|
|
->assertJsonPath('title', 'Servers');
|
|
});
|
|
|
|
test('the remembered view belongs to the user, not to the browser', function () {
|
|
$other = User::factory()->create();
|
|
$this->team->members()->attach($other->id, ['role' => 'admin']);
|
|
|
|
serverCanvasSession($this, $other, $this->team)
|
|
->withCookie("coolify_{$this->admin->id}_servers_view", 'canvas')
|
|
->get(route('server.index'))
|
|
->assertOk();
|
|
});
|
|
|
|
test('the list page remembers the table or grid choice and opens it again', function () {
|
|
$this->actingAs($this->admin)->withSession(['currentTeam' => $this->team]);
|
|
$cookie = "coolify_{$this->admin->id}_servers_view";
|
|
|
|
Livewire::test(ServerIndex::class)->call('saveListView', 'servers', 'grid');
|
|
expect(Cookie::queued($cookie)?->getValue())->toBe('grid');
|
|
|
|
// Unknown pages and views are ignored.
|
|
Cookie::flushQueuedCookies();
|
|
Livewire::test(ServerIndex::class)
|
|
->call('saveListView', 'servers', 'canvas')
|
|
->call('saveListView', 'billing', 'grid');
|
|
expect(Cookie::getQueuedCookies())->toBeEmpty();
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->withCookie($cookie, 'grid')
|
|
->get(route('server.index'))
|
|
->assertOk()
|
|
->assertSee("viewMode: 'grid'", false);
|
|
});
|
|
|
|
test('one server canvas loads the unmanaged containers of that server', function () {
|
|
$server = Mockery::mock($this->web)->makePartial();
|
|
$server->shouldReceive('loadUnmanagedContainers')->andReturn(collect([
|
|
['ID' => 'abc123', 'Names' => 'portainer', 'Image' => 'portainer/portainer-ce', 'State' => 'running', 'Status' => 'Up 2 hours (healthy)'],
|
|
['ID' => 'def456', 'Names' => 'old-job', 'Image' => 'busybox', 'State' => 'exited', 'Status' => 'Exited (0) 3 days ago'],
|
|
]));
|
|
|
|
$nodes = app(ServerCanvas::class)->unmanagedContainerNodes($server);
|
|
|
|
expect($nodes->map(fn ($node) => [$node['id'], $node['type'], $node['name'], $node['status'], $node['subtitle'], $node['zone'], $node['context']])->all())->toBe([
|
|
['container:def456', 'container', 'old-job', 'exited', 'busybox', $this->web->uuid, 'Unmanaged containers'],
|
|
['container:abc123', 'container', 'portainer', 'running:healthy', 'portainer/portainer-ce', $this->web->uuid, 'Unmanaged containers'],
|
|
]);
|
|
});
|
|
|
|
test('the server canvas still loads when the unmanaged containers cannot be read', function () {
|
|
$server = Mockery::mock($this->web)->makePartial();
|
|
$server->shouldReceive('loadUnmanagedContainers')->andThrow(new RuntimeException('SSH failed'));
|
|
|
|
expect(app(ServerCanvas::class)->unmanagedContainerNodes($server)->all())->toBe([]);
|
|
});
|
|
|
|
test('only the one server canvas points to the unmanaged containers of its server', function () {
|
|
$otherTeam = Team::factory()->create(['show_boarding' => false]);
|
|
$foreign = Server::factory()->create(['team_id' => $otherTeam->id]);
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson("/canvas/api/servers/{$this->web->uuid}/canvas")
|
|
->assertJsonPath('containers_endpoint', "/servers/{$this->web->uuid}/canvas/containers");
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson('/canvas/api/servers/canvas')
|
|
->assertJsonPath('containers_endpoint', null);
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson("/canvas/api/servers/{$this->web->uuid}/canvas/containers")
|
|
->assertOk()->assertExactJson(['nodes' => []]);
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson("/canvas/api/servers/{$foreign->uuid}/canvas/containers")
|
|
->assertNotFound();
|
|
});
|
|
|
|
test('server zones show host facts, the proxy and a usage endpoint only when metrics can be read', function () {
|
|
$this->web->forceFill([
|
|
'server_metadata' => ['os' => 'Ubuntu 24.04', 'arch' => 'x86_64', 'cpus' => 4, 'memory_bytes' => 8589934592, 'uptime_since' => '2026-10-01 10:00:00'],
|
|
'proxy' => ['type' => 'TRAEFIK', 'status' => 'running'],
|
|
])->save();
|
|
$this->web->settings->forceFill(['is_reachable' => true, 'is_metrics_enabled' => true])->saveQuietly();
|
|
$this->db->settings->forceFill(['is_reachable' => true, 'is_metrics_enabled' => false])->saveQuietly();
|
|
$this->db->forceFill(['proxy' => ['type' => 'NONE']])->save();
|
|
|
|
$zones = collect(serverCanvasSession($this, $this->admin, $this->team)
|
|
->getJson('/canvas/api/servers/canvas')
|
|
->assertOk()
|
|
->json('zones'))->keyBy('name');
|
|
|
|
expect($zones['web-server']['details'])->toBe([
|
|
'os' => 'Ubuntu 24.04',
|
|
'arch' => 'x86_64',
|
|
'cpus' => 4,
|
|
'memory_bytes' => 8589934592,
|
|
'uptime_since' => '2026-10-01 10:00:00',
|
|
'proxy' => ['type' => 'TRAEFIK', 'status' => 'running'],
|
|
'usage_endpoint' => "/servers/{$this->web->uuid}/canvas/usage",
|
|
])
|
|
->and($zones['db-server']['details']['proxy'])->toBeNull()
|
|
->and($zones['db-server']['details']['usage_endpoint'])->toBeNull();
|
|
});
|
|
|
|
test('the usage endpoint returns the latest Sentinel values and never another team server', function () {
|
|
$server = Mockery::mock($this->web)->makePartial();
|
|
$server->shouldReceive('prefetchMetrics')->once()->with(['cpu', 'memory', 'disk'], 1);
|
|
$server->shouldReceive('getCpuMetrics')->andReturn([[1, 10.0], [2, 12.34]]);
|
|
$server->shouldReceive('getMemoryMetrics')->andReturn([[2, 40.0]]);
|
|
$server->shouldReceive('getDiskMetrics')->andThrow(new RuntimeException('no disk data'));
|
|
|
|
expect(app(ServerCanvas::class)->usage($server))->toBe(['cpu' => 12.3, 'memory' => 40.0, 'disk' => null]);
|
|
|
|
$this->web->settings->forceFill(['is_metrics_enabled' => false])->saveQuietly();
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson("/canvas/api/servers/{$this->web->uuid}/canvas/usage")
|
|
->assertOk()->assertExactJson(['cpu' => null, 'memory' => null, 'disk' => null]);
|
|
|
|
$otherTeam = Team::factory()->create(['show_boarding' => false]);
|
|
$foreign = Server::factory()->create(['team_id' => $otherTeam->id]);
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson("/canvas/api/servers/{$foreign->uuid}/canvas/usage")
|
|
->assertNotFound();
|
|
});
|
|
|
|
test('only admins may move the environment groups on the server canvas', function () {
|
|
$member = User::factory()->create();
|
|
$this->team->members()->attach($member->id, ['role' => 'member']);
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson('/canvas/api/servers/canvas')
|
|
->assertJsonPath('can_move_groups', true);
|
|
serverCanvasSession($this, $member, $this->team)->getJson('/canvas/api/servers/canvas')
|
|
->assertJsonPath('can_move_groups', false);
|
|
});
|
|
|
|
test('an admin moves server zones on the servers and dashboard canvases and both return their place', function () {
|
|
// Another team's server with the same request stays as it is.
|
|
$foreign = Server::factory()->create(['team_id' => Team::factory()->create()->id]);
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson('/canvas/api/servers/canvas')
|
|
->assertJsonPath('can_move_zones', true)
|
|
->assertJsonPath('zones.0.position', null);
|
|
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->patchJson('/canvas/api/dashboard/canvas/positions', ['positions' => [
|
|
$this->web->uuid => ['x' => 640, 'y' => -120],
|
|
$foreign->uuid => ['x' => 5, 'y' => 5],
|
|
'no-server' => ['x' => 0, 'y' => 0],
|
|
]])
|
|
->assertOk()
|
|
->assertJsonPath('positions', [$this->web->uuid => ['x' => 640, 'y' => -120]]);
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->patchJson('/canvas/api/servers/canvas/positions', ['positions' => [$this->db->uuid => ['x' => 0, 'y' => 0]]])
|
|
->assertOk();
|
|
|
|
expect($foreign->fresh()->canvas_position)->toBeNull();
|
|
// Zones are sorted by name: db-server, web-server.
|
|
foreach (['/canvas/api/servers/canvas', '/canvas/api/dashboard/canvas'] as $endpoint) {
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson($endpoint)
|
|
->assertJsonPath('zones.0.position', ['x' => 0, 'y' => 0])
|
|
->assertJsonPath('zones.1.position', ['x' => 640, 'y' => -120]);
|
|
}
|
|
// The one server canvas has a single zone, which stays in its place.
|
|
serverCanvasSession($this, $this->admin, $this->team)->getJson("/canvas/api/servers/{$this->web->uuid}/canvas")
|
|
->assertJsonPath('can_move_zones', false)
|
|
->assertJsonPath('zones.0.position', null);
|
|
});
|
|
|
|
test('server zone positions must be whole coordinates on the canvas', function () {
|
|
serverCanvasSession($this, $this->admin, $this->team)
|
|
->patchJson('/canvas/api/servers/canvas/positions', ['positions' => [$this->web->uuid => ['x' => 'left', 'y' => 999999999]]])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors(["positions.{$this->web->uuid}.x", "positions.{$this->web->uuid}.y"]);
|
|
|
|
expect($this->web->fresh()->canvas_position)->toBeNull();
|
|
});
|
|
|
|
test('members and other teams cannot move a server zone', function () {
|
|
$member = User::factory()->create();
|
|
$this->team->members()->attach($member->id, ['role' => 'member']);
|
|
serverCanvasSession($this, $member, $this->team)->getJson('/canvas/api/dashboard/canvas')
|
|
->assertJsonPath('can_move_zones', false);
|
|
serverCanvasSession($this, $member, $this->team)
|
|
->patchJson('/canvas/api/servers/canvas/positions', ['positions' => [$this->web->uuid => ['x' => 10, 'y' => 10]]])
|
|
->assertForbidden();
|
|
|
|
// Admin of this team, owner of another team, with the session switched to the other team.
|
|
$otherTeam = Team::factory()->create(['show_boarding' => false]);
|
|
$otherTeam->members()->attach($this->admin->id, ['role' => 'owner']);
|
|
serverCanvasSession($this, $this->admin, $otherTeam)
|
|
->patchJson('/canvas/api/dashboard/canvas/positions', ['positions' => [$this->web->uuid => ['x' => 10, 'y' => 10]]])
|
|
->assertOk()
|
|
->assertJsonPath('positions', []);
|
|
|
|
expect($this->web->fresh()->canvas_position)->toBeNull();
|
|
});
|