Files
coolify/tests/Feature/SharedVariablesOverviewTest.php
Andras Bacsai fa1895c99a feat(dashboard): add canvas view with active deployment overlay
Add a /dashboard/canvas page that renders every team server with its
resources and their running or queued deployments, backed by a new
ServerCanvasController::dashboard endpoint and a per-server usage
endpoint for zone headers. The dashboard remembers the list/canvas
choice and each section (projects, servers) its own table/grid view.

- Show Deploying/Queued pills on canvas cards and a foldable recent
  deployments panel; add shared CanvasControls component
- Allow backup downloads from the server host via the S3 download
  storage (PrepareFileDownloadJob accepts a null container)
- Extract backup toggle/run actions into ManagesDatabaseBackup and
  surface them in backup execution headers; merge S3 and retention
  sections into the general backup settings
- Move deployments indicator to the top bar and add a main nav
  resource menu with submenus; simplify breadcrumbs and settings
  layouts
- Add shared variables overview canvas and scheduled task modals
- Add feature tests for dashboard canvas, view modes, navbar
  submenus, backup S3 downloads and scheduled task modals
2026-10-14 23:55:28 +02:00

91 lines
4.1 KiB
PHP

<?php
use App\Models\Application;
use App\Models\Environment;
use App\Models\EnvironmentVariable;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\Server;
use App\Models\SharedEnvironmentVariable;
use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
use App\Services\SharedVariableCanvas;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0]));
Server::flushIdentityMap();
$this->team = Team::factory()->create(['show_boarding' => false]);
$this->user = User::factory()->create();
$this->team->members()->attach($this->user->id, ['role' => 'member']);
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
$this->project = Project::factory()->create(['team_id' => $this->team->id, 'name' => 'Shop']);
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
$destination = StandaloneDocker::query()->where('server_id', $this->server->id)->firstOrFail();
$this->application = Application::factory()->create([
'environment_id' => $this->environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
});
function overviewShared(array $attributes): SharedEnvironmentVariable
{
return SharedEnvironmentVariable::create(['value' => 'secret-value', ...$attributes]);
}
function overviewEnv(Application $application, string $key, string $value): void
{
EnvironmentVariable::create([
'key' => $key, 'value' => $value,
'resourceable_type' => $application->getMorphClass(), 'resourceable_id' => $application->id,
]);
}
test('the overview counts variables per scope and the resources that use each one', function () {
overviewShared(['key' => 'API_TOKEN', 'type' => 'team', 'team_id' => $this->team->id]);
overviewShared(['key' => 'DB_URL', 'type' => 'project', 'team_id' => $this->team->id, 'project_id' => $this->project->id]);
overviewShared(['key' => 'REGION', 'type' => 'server', 'team_id' => $this->team->id, 'server_id' => $this->server->id]);
overviewEnv($this->application, 'TOKEN', '{{team.API_TOKEN}}');
overviewEnv($this->application, 'DATABASE', 'postgres://{{ project.DB_URL }}');
overviewEnv($this->application, 'OLD', '{{team.REMOVED}}');
$overview = app(SharedVariableCanvas::class)->overview($this->team);
expect($overview['scopes']['team'])->toBe(['variables' => 1, 'owners' => 1])
->and($overview['scopes']['project'])->toBe(['variables' => 1, 'owners' => 1])
->and($overview['scopes']['environment'])->toBe(['variables' => 0, 'owners' => 0])
->and($overview['used']->pluck('resources', 'key')->all())->toBe(['API_TOKEN' => 1, 'DB_URL' => 1])
->and($overview['unused']->pluck('key')->all())->toBe(['REGION'])
->and($overview['missing'])->toBe(1);
});
test('a reference resolves against the resource own project, not another project with the same key', function () {
$otherProject = Project::factory()->create(['team_id' => $this->team->id]);
overviewShared(['key' => 'DB_URL', 'type' => 'project', 'team_id' => $this->team->id, 'project_id' => $otherProject->id]);
overviewEnv($this->application, 'DATABASE', '{{project.DB_URL}}');
$overview = app(SharedVariableCanvas::class)->overview($this->team);
expect($overview['used'])->toBeEmpty()
->and($overview['missing'])->toBe(1);
});
test('the overview page shows only the current team and never the values', function () {
overviewShared(['key' => 'API_TOKEN', 'type' => 'team', 'team_id' => $this->team->id]);
$otherTeam = Team::factory()->create();
overviewShared(['key' => 'OTHER_TEAM_KEY', 'type' => 'team', 'team_id' => $otherTeam->id]);
$this->actingAs($this->user)->withSession(['currentTeam' => $this->team])
->get(route('shared-variables.index'))
->assertSuccessful()
->assertSee('API_TOKEN')
->assertDontSee('OTHER_TEAM_KEY')
->assertDontSee('secret-value');
});