Files
coolify/tests/Feature/ProjectCanvasTest.php
T

210 lines
9.3 KiB
PHP

<?php
use App\Livewire\Project\ProjectCanvas;
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 Illuminate\Foundation\Testing\RefreshDatabase;
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->server = Server::factory()->create(['team_id' => $this->team->id, 'name' => 'web-server']);
$this->project = Project::factory()->create(['team_id' => $this->team->id, 'name' => 'Shop']);
$this->staging = Environment::factory()->create(['project_id' => $this->project->id, 'name' => 'staging']);
$this->production = $this->project->environments()->where('name', 'production')->firstOrFail();
});
function projectCanvasApp(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 projectCanvasSession(object $test, User $user, Team $team): object
{
return $test->actingAs($user)->withSession(['currentTeam' => $team])
->withHeader('X-Canvas-Team', (string) $team->id);
}
test('the project canvas shows the resources of every environment, grouped by environment', function () {
$app = projectCanvasApp($this->staging, $this->server, 'storefront');
$api = projectCanvasApp($this->staging, $this->server, 'orders-api');
$live = projectCanvasApp($this->production, $this->server, 'storefront-live');
$link = ResourceLink::create([
'environment_id' => $this->staging->id,
'source_type' => $app->getMorphClass(), 'source_id' => $app->id,
'target_type' => $api->getMorphClass(), 'target_id' => $api->id,
]);
$this->production->forceFill(['canvas_positions' => [$live->uuid => ['x' => 40, 'y' => 80]]])->save();
// A resource of another project of the same team stays out.
$other = Environment::factory()->create(['project_id' => Project::factory()->create(['team_id' => $this->team->id])->id]);
projectCanvasApp($other, $this->server, 'elsewhere');
$response = projectCanvasSession($this, $this->admin, $this->team)
->getJson("/canvas/api/projects/{$this->project->uuid}/canvas")
->assertOk()
->assertJsonPath('title', 'Shop')
->assertJsonPath('can_edit', true)
->assertJsonPath('layout', 'server')
->assertJsonPath('links.0.id', $link->uuid)
->assertJsonPath('positions', [$live->uuid => ['x' => 40, 'y' => 80]]);
expect(collect($response->json('zones'))->pluck('name')->all())->toBe(['web-server'])
->and(collect($response->json('nodes'))->mapWithKeys(fn ($node) => [$node['name'] => $node['context']])->sortKeys()->all())->toBe([
'orders-api' => 'staging',
'storefront' => 'staging',
'storefront-live' => 'production',
])
// The canvas saves moves and links on the environment canvas of each card.
->and(collect($response->json('nodes'))->firstWhere('name', 'storefront-live')['environment'])->toBe(['uuid' => $this->production->uuid, 'name' => 'production']);
});
test('members see the project canvases but cannot edit them', function () {
$member = User::factory()->create();
$this->team->members()->attach($member->id, ['role' => 'member']);
projectCanvasSession($this, $member, $this->team)
->getJson("/canvas/api/projects/{$this->project->uuid}/canvas")
->assertOk()
->assertJsonPath('can_edit', false);
projectCanvasSession($this, $member, $this->team)
->getJson('/canvas/api/projects/canvas')
->assertOk()
->assertJsonPath('can_edit', false);
});
test('the project canvas is out of reach from another team', 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']);
// Session switched to the other team: this team's project is out of reach.
projectCanvasSession($this, $outsider, $otherTeam)
->getJson("/canvas/api/projects/{$this->project->uuid}/canvas")
->assertNotFound();
projectCanvasSession($this, $outsider, $otherTeam)
->get(route('project.canvas', ['project_uuid' => $this->project->uuid]))
->assertNotFound();
});
test('the project page reopens the canvas when the user left it on the canvas view', function () {
$parameters = ['project_uuid' => $this->project->uuid];
projectCanvasSession($this, $this->admin, $this->team)
->withoutVite()
->get(route('project.canvas', $parameters))
->assertOk()
->assertSee('data-endpoint="/projects/'.$this->project->uuid.'/canvas"', false)
->assertCookie("coolify_{$this->admin->id}_project_view", 'canvas');
projectCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_project_view", 'canvas')
->get(route('project.show', $parameters))
->assertRedirect(route('project.canvas', $parameters));
// The table / grid links on the canvas pick the list view again.
projectCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_project_view", 'canvas')
->get(route('project.show', [...$parameters, 'view' => 'grid']))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_project_view", 'grid');
});
test('the projects canvas shows every project of the team as a zone, grouped by environment', function () {
$app = projectCanvasApp($this->staging, $this->server, 'storefront');
$blog = Project::factory()->create(['team_id' => $this->team->id, 'name' => 'Blog']);
$post = projectCanvasApp($blog->environments()->firstOrFail(), $this->server, 'wordpress');
// Another team's project stays out.
$otherTeam = Team::factory()->create();
$otherServer = Server::factory()->create(['team_id' => $otherTeam->id]);
$foreign = Project::factory()->create(['team_id' => $otherTeam->id, 'name' => 'Foreign']);
projectCanvasApp($foreign->environments()->firstOrFail(), $otherServer, 'secret-app');
$response = projectCanvasSession($this, $this->admin, $this->team)
->getJson('/canvas/api/projects/canvas')
->assertOk()
->assertJsonPath('title', 'Projects')
->assertJsonPath('can_edit', true)
->assertJsonPath('layout', 'server');
expect(collect($response->json('zones'))->pluck('name')->all())->toBe(['Blog', 'Shop'])
->and(collect($response->json('nodes'))->mapWithKeys(fn ($node) => [$node['name'] => [$node['zone'], $node['context']]])->sortKeys()->all())->toBe([
'storefront' => [$this->project->uuid, 'staging'],
'wordpress' => [$blog->uuid, 'production'],
]);
});
test('the projects page reopens the canvas when the user left it on the canvas view', function () {
projectCanvasSession($this, $this->admin, $this->team)
->get(route('project.canvas.index'))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_projects_view", 'canvas');
projectCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_projects_view", 'canvas')
->get(route('project.index'))
->assertRedirect(route('project.canvas.index'));
projectCanvasSession($this, $this->admin, $this->team)
->withCookie("coolify_{$this->admin->id}_projects_view", 'canvas')
->get(route('project.index', ['view' => 'table']))
->assertOk()
->assertCookie("coolify_{$this->admin->id}_projects_view", 'table');
});
test('an admin creates an environment from the project canvas', function () {
$this->actingAs($this->admin);
session(['currentTeam' => $this->team]);
Livewire::test(ProjectCanvas::class, ['project_uuid' => $this->project->uuid])
->set('name', 'testing')
->call('submit')
->assertRedirect();
expect($this->project->environments()->where('name', 'testing')->exists())->toBeTrue();
});
test('a member cannot create an environment from the project canvas', function () {
$member = User::factory()->create();
$this->team->members()->attach($member->id, ['role' => 'member']);
$this->actingAs($member);
session(['currentTeam' => $this->team]);
Livewire::test(ProjectCanvas::class, ['project_uuid' => $this->project->uuid])
->set('name', 'testing')
->call('submit')
->assertDispatched('error');
expect($this->project->environments()->where('name', 'testing')->exists())->toBeFalse();
});