test(auth): cover authorization scenarios for api and ui
This commit is contained in:
parent
52b88135f3
commit
34d8499a0c
12 changed files with 2477 additions and 6 deletions
218
tests/Feature/ApiAuthorizationTest.php
Normal file
218
tests/Feature/ApiAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0], ['is_api_enabled' => true]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
$keyId = DB::table('private_keys')->insertGetId([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Key',
|
||||
'private_key' => 'test-key',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $keyId,
|
||||
]);
|
||||
|
||||
StandaloneDocker::withoutEvents(function () {
|
||||
$this->destination = StandaloneDocker::firstOrCreate(
|
||||
['server_id' => $this->server->id, 'network' => 'coolify'],
|
||||
['uuid' => (string) Str::uuid(), 'name' => 'test-docker']
|
||||
);
|
||||
});
|
||||
|
||||
$this->project = Project::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$this->environment = $this->project->environments()->first();
|
||||
|
||||
$this->application = Application::factory()->create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test App',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
'status' => 'running',
|
||||
]);
|
||||
|
||||
$this->database = StandalonePostgresql::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test DB',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'testdb',
|
||||
'image' => 'postgres:15',
|
||||
'status' => 'running',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
]);
|
||||
|
||||
// Create real tokens with team_id (createToken reads from session)
|
||||
session(['currentTeam' => $this->team]);
|
||||
$this->adminRootToken = $this->admin->createToken('admin-root', ['root']);
|
||||
$this->adminReadToken = $this->admin->createToken('admin-read', ['read']);
|
||||
$this->memberRootToken = $this->member->createToken('member-root', ['root']);
|
||||
});
|
||||
|
||||
// --- Unauthenticated Access ---
|
||||
|
||||
test('unauthenticated request to api returns 401', function () {
|
||||
$this->getJson('/api/v1/projects')->assertStatus(401);
|
||||
});
|
||||
|
||||
// --- Admin with root token ---
|
||||
|
||||
test('admin with root token can list projects', function () {
|
||||
$this->withToken($this->adminRootToken->plainTextToken)
|
||||
->getJson('/api/v1/projects')
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('admin with root token can view project', function () {
|
||||
$this->withToken($this->adminRootToken->plainTextToken)
|
||||
->getJson("/api/v1/projects/{$this->project->uuid}")
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('admin with root token can view application', function () {
|
||||
$this->withToken($this->adminRootToken->plainTextToken)
|
||||
->getJson("/api/v1/applications/{$this->application->uuid}")
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('admin with root token can view server', function () {
|
||||
$this->withToken($this->adminRootToken->plainTextToken)
|
||||
->getJson("/api/v1/servers/{$this->server->uuid}")
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('admin with root token can view database', function () {
|
||||
$this->withToken($this->adminRootToken->plainTextToken)
|
||||
->getJson("/api/v1/databases/{$this->database->uuid}")
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
// --- Member with root token (policy should deny mutations) ---
|
||||
|
||||
test('member with root token can view project', function () {
|
||||
$this->withToken($this->memberRootToken->plainTextToken)
|
||||
->getJson("/api/v1/projects/{$this->project->uuid}")
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
test('member with root token cannot delete project', function () {
|
||||
$this->withToken($this->memberRootToken->plainTextToken)
|
||||
->deleteJson("/api/v1/projects/{$this->project->uuid}")
|
||||
->assertStatus(403);
|
||||
});
|
||||
|
||||
test('member with root token cannot update application', function () {
|
||||
$this->withToken($this->memberRootToken->plainTextToken)
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", [
|
||||
'name' => 'New Name',
|
||||
])
|
||||
->assertStatus(403);
|
||||
});
|
||||
|
||||
test('member with root token cannot delete application', function () {
|
||||
$this->withToken($this->memberRootToken->plainTextToken)
|
||||
->deleteJson("/api/v1/applications/{$this->application->uuid}")
|
||||
->assertStatus(403);
|
||||
});
|
||||
|
||||
test('member with root token cannot delete server', function () {
|
||||
$this->withToken($this->memberRootToken->plainTextToken)
|
||||
->deleteJson("/api/v1/servers/{$this->server->uuid}")
|
||||
->assertStatus(403);
|
||||
});
|
||||
|
||||
test('member with root token cannot update server', function () {
|
||||
$this->withToken($this->memberRootToken->plainTextToken)
|
||||
->patchJson("/api/v1/servers/{$this->server->uuid}", [
|
||||
'name' => 'New Server Name',
|
||||
])
|
||||
->assertStatus(403);
|
||||
});
|
||||
|
||||
// --- Token ability checks ---
|
||||
|
||||
test('read-only token cannot create project', function () {
|
||||
$this->withToken($this->adminReadToken->plainTextToken)
|
||||
->postJson('/api/v1/projects', [
|
||||
'name' => 'New Project',
|
||||
])
|
||||
->assertStatus(403);
|
||||
});
|
||||
|
||||
test('read-only token cannot delete application', function () {
|
||||
$this->withToken($this->adminReadToken->plainTextToken)
|
||||
->deleteJson("/api/v1/applications/{$this->application->uuid}")
|
||||
->assertStatus(403);
|
||||
});
|
||||
|
||||
// --- Cross-team isolation ---
|
||||
|
||||
test('user from different team cannot view project via api', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
session(['currentTeam' => $otherTeam]);
|
||||
$otherToken = $otherUser->createToken('other-root', ['root']);
|
||||
|
||||
$this->withToken($otherToken->plainTextToken)
|
||||
->getJson("/api/v1/projects/{$this->project->uuid}")
|
||||
->assertStatus(404);
|
||||
});
|
||||
|
||||
test('user from different team cannot view application via api', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
session(['currentTeam' => $otherTeam]);
|
||||
$otherToken = $otherUser->createToken('other-root', ['root']);
|
||||
|
||||
$this->withToken($otherToken->plainTextToken)
|
||||
->getJson("/api/v1/applications/{$this->application->uuid}")
|
||||
->assertStatus(404);
|
||||
});
|
||||
|
||||
test('user from different team cannot view server via api', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
session(['currentTeam' => $otherTeam]);
|
||||
$otherToken = $otherUser->createToken('other-root', ['root']);
|
||||
|
||||
$this->withToken($otherToken->plainTextToken)
|
||||
->getJson("/api/v1/servers/{$this->server->uuid}")
|
||||
->assertStatus(404);
|
||||
});
|
||||
243
tests/Feature/ApplicationConfigAuthorizationTest.php
Normal file
243
tests/Feature/ApplicationConfigAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Project\Application\Advanced as ApplicationAdvanced;
|
||||
use App\Livewire\Project\Application\Heading as ApplicationHeading;
|
||||
use App\Livewire\Project\Application\Rollback as ApplicationRollback;
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
$keyId = DB::table('private_keys')->insertGetId([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Key',
|
||||
'private_key' => 'test-key',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $keyId,
|
||||
]);
|
||||
|
||||
$this->server->settings()->update([
|
||||
'is_reachable' => true,
|
||||
'is_usable' => true,
|
||||
]);
|
||||
|
||||
StandaloneDocker::withoutEvents(function () {
|
||||
$this->destination = StandaloneDocker::firstOrCreate(
|
||||
['server_id' => $this->server->id, 'network' => 'coolify'],
|
||||
['uuid' => (string) Str::uuid(), 'name' => 'test-docker']
|
||||
);
|
||||
});
|
||||
|
||||
$this->project = Project::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$this->environment = $this->project->environments()->first();
|
||||
|
||||
$this->application = Application::factory()->create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test App',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
'status' => 'running',
|
||||
]);
|
||||
});
|
||||
|
||||
// --- Application Policy: view ---
|
||||
|
||||
test('admin can view application', function () {
|
||||
expect($this->admin->can('view', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member can view application', function () {
|
||||
expect($this->member->can('view', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Application Policy: update ---
|
||||
|
||||
test('admin can update application', function () {
|
||||
expect($this->admin->can('update', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update application', function () {
|
||||
expect($this->member->can('update', $this->application))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Application Policy: deploy ---
|
||||
|
||||
test('admin can deploy application', function () {
|
||||
expect($this->admin->can('deploy', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot deploy application', function () {
|
||||
expect($this->member->can('deploy', $this->application))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Application Policy: delete ---
|
||||
|
||||
test('admin can delete application', function () {
|
||||
expect($this->admin->can('delete', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete application', function () {
|
||||
expect($this->member->can('delete', $this->application))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Application Policy: manageEnvironment ---
|
||||
|
||||
test('admin can manage application environment', function () {
|
||||
expect($this->admin->can('manageEnvironment', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage application environment', function () {
|
||||
expect($this->member->can('manageEnvironment', $this->application))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Application Heading Livewire actions ---
|
||||
|
||||
test('member cannot call deploy on application heading', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->call('deploy')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot call restart on application heading', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->call('restart')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot call stop on application heading', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->call('stop')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot call force deploy on application heading', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->call('force_deploy_without_cache')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Application General policy (Livewire mount requires full app data) ---
|
||||
|
||||
test('member cannot update application general settings', function () {
|
||||
expect($this->member->can('update', $this->application))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Application Advanced Livewire actions ---
|
||||
|
||||
test('member cannot save application advanced settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationAdvanced::class, ['application' => $this->application])
|
||||
->call('instantSave')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot submit application advanced settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationAdvanced::class, ['application' => $this->application])
|
||||
->call('submit')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Application Rollback Livewire actions ---
|
||||
|
||||
test('member cannot save rollback settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationRollback::class, ['application' => $this->application])
|
||||
->call('saveSettings')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot rollback image', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationRollback::class, ['application' => $this->application])
|
||||
->call('rollbackImage', 'test-image:latest')
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
// --- Application Heading visibility ---
|
||||
|
||||
test('member does not see terminal link for application', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->assertDontSee('Terminal');
|
||||
});
|
||||
|
||||
test('admin sees terminal link for application', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->assertSee('Terminal');
|
||||
});
|
||||
|
||||
// --- Cross-team isolation ---
|
||||
|
||||
test('user from different team cannot view application', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('view', $this->application))->toBeFalse();
|
||||
});
|
||||
|
||||
test('user from different team cannot update application', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('update', $this->application))->toBeFalse();
|
||||
});
|
||||
162
tests/Feature/CloudProviderAuthorizationTest.php
Normal file
162
tests/Feature/CloudProviderAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
use App\Models\CloudInitScript;
|
||||
use App\Models\CloudProviderToken;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PersonalAccessToken;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
DB::table('private_keys')->insert([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Team SSH Key',
|
||||
'description' => 'Key for testing',
|
||||
'private_key' => 'test-key-content',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->privateKey = PrivateKey::where('team_id', $this->team->id)->first();
|
||||
});
|
||||
|
||||
// --- Private Key Policy ---
|
||||
|
||||
test('admin can create private key', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', PrivateKey::class))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot create private key', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', PrivateKey::class))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can view private key', function () {
|
||||
expect($this->admin->can('view', $this->privateKey))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member can view own team private key', function () {
|
||||
expect($this->member->can('view', $this->privateKey))->toBeTrue();
|
||||
});
|
||||
|
||||
test('admin can update private key', function () {
|
||||
expect($this->admin->can('update', $this->privateKey))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update private key', function () {
|
||||
expect($this->member->can('update', $this->privateKey))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can delete private key', function () {
|
||||
expect($this->admin->can('delete', $this->privateKey))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete private key', function () {
|
||||
expect($this->member->can('delete', $this->privateKey))->toBeFalse();
|
||||
});
|
||||
|
||||
test('user from different team cannot view private key', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('view', $this->privateKey))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Cloud Provider Token Policy ---
|
||||
|
||||
test('admin can create cloud provider token', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', CloudProviderToken::class))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot create cloud provider token', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', CloudProviderToken::class))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can view any cloud provider tokens', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('viewAny', CloudProviderToken::class))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Cloud Init Script Policy ---
|
||||
|
||||
test('admin can create cloud init script', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', CloudInitScript::class))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot create cloud init script', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', CloudInitScript::class))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can view any cloud init scripts', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('viewAny', CloudInitScript::class))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Personal Access Token (API Token) Policy ---
|
||||
|
||||
test('any user can create personal access token', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', PersonalAccessToken::class))->toBeTrue();
|
||||
});
|
||||
|
||||
test('admin can use root permissions for api tokens', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('useRootPermissions', PersonalAccessToken::class))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot use root permissions for api tokens', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('useRootPermissions', PersonalAccessToken::class))->toBeFalse();
|
||||
});
|
||||
|
||||
test('member cannot use write permissions for api tokens', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('useWritePermissions', PersonalAccessToken::class))->toBeFalse();
|
||||
});
|
||||
188
tests/Feature/DatabaseConfigAuthorizationTest.php
Normal file
188
tests/Feature/DatabaseConfigAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
$keyId = DB::table('private_keys')->insertGetId([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Key',
|
||||
'private_key' => 'test-key',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $keyId,
|
||||
]);
|
||||
|
||||
StandaloneDocker::withoutEvents(function () {
|
||||
$this->destination = StandaloneDocker::firstOrCreate(
|
||||
['server_id' => $this->server->id, 'network' => 'coolify'],
|
||||
['uuid' => (string) Str::uuid(), 'name' => 'test-docker']
|
||||
);
|
||||
});
|
||||
|
||||
$this->project = Project::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$this->environment = $this->project->environments()->first();
|
||||
|
||||
$this->database = StandalonePostgresql::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test DB',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'testdb',
|
||||
'image' => 'postgres:15',
|
||||
'status' => 'running',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
]);
|
||||
|
||||
$this->databaseUrl = "/project/{$this->project->uuid}/environment/{$this->environment->uuid}/database/{$this->database->uuid}";
|
||||
});
|
||||
|
||||
// --- Database Policy: view ---
|
||||
|
||||
test('admin can view database', function () {
|
||||
expect($this->admin->can('view', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member can view database', function () {
|
||||
expect($this->member->can('view', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Database Policy: update ---
|
||||
|
||||
test('admin can update database', function () {
|
||||
expect($this->admin->can('update', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update database', function () {
|
||||
expect($this->member->can('update', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Database Policy: delete ---
|
||||
|
||||
test('admin can delete database', function () {
|
||||
expect($this->admin->can('delete', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete database', function () {
|
||||
expect($this->member->can('delete', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Database Policy: manage ---
|
||||
|
||||
test('admin can manage database', function () {
|
||||
expect($this->admin->can('manage', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage database', function () {
|
||||
expect($this->member->can('manage', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Database Policy: manageBackups ---
|
||||
|
||||
test('admin can manage database backups', function () {
|
||||
expect($this->admin->can('manageBackups', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage database backups', function () {
|
||||
expect($this->member->can('manageBackups', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Database Policy: manageEnvironment ---
|
||||
|
||||
test('admin can manage database environment variables', function () {
|
||||
expect($this->admin->can('manageEnvironment', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage database environment variables', function () {
|
||||
expect($this->member->can('manageEnvironment', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Database page access ---
|
||||
|
||||
test('admin can access database page', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get($this->databaseUrl)->assertSuccessful();
|
||||
});
|
||||
|
||||
test('member can access database page', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get($this->databaseUrl)->assertSuccessful();
|
||||
});
|
||||
|
||||
// --- Terminal access ---
|
||||
|
||||
test('admin can access terminal for database', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('canAccessTerminal'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot access terminal for database', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('canAccessTerminal'))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Cross-team isolation ---
|
||||
|
||||
test('user from different team cannot view database', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('view', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
test('user from different team cannot update database', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('update', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
test('user from different team cannot manage database', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('manage', $this->database))->toBeFalse();
|
||||
});
|
||||
277
tests/Feature/NotificationAuthorizationTest.php
Normal file
277
tests/Feature/NotificationAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Notifications\Discord as DiscordNotification;
|
||||
use App\Livewire\Notifications\Email as EmailNotification;
|
||||
use App\Livewire\Notifications\Pushover as PushoverNotification;
|
||||
use App\Livewire\Notifications\Slack as SlackNotification;
|
||||
use App\Livewire\Notifications\Telegram as TelegramNotification;
|
||||
use App\Livewire\Notifications\Webhook as WebhookNotification;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
});
|
||||
|
||||
// --- Discord ---
|
||||
|
||||
test('member cannot send test notification on discord', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->discordNotificationSettings;
|
||||
$settings->update([
|
||||
'discord_enabled' => true,
|
||||
'discord_webhook_url' => 'https://discord.com/api/webhooks/test',
|
||||
]);
|
||||
|
||||
Livewire::test(DiscordNotification::class)
|
||||
->call('sendTestNotification')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot update discord notification settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(DiscordNotification::class)
|
||||
->call('syncData', true)
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('admin can update discord notification settings', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->discordNotificationSettings;
|
||||
|
||||
expect($this->admin->can('update', $settings))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Slack ---
|
||||
|
||||
test('member cannot send test notification on slack', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->slackNotificationSettings;
|
||||
$settings->update([
|
||||
'slack_enabled' => true,
|
||||
'slack_webhook_url' => 'https://hooks.slack.com/services/test',
|
||||
]);
|
||||
|
||||
Livewire::test(SlackNotification::class)
|
||||
->call('sendTestNotification')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot update slack notification settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(SlackNotification::class)
|
||||
->call('syncData', true)
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('admin can update slack notification settings', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->slackNotificationSettings;
|
||||
|
||||
expect($this->admin->can('update', $settings))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Telegram ---
|
||||
|
||||
test('member cannot send test notification on telegram', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->telegramNotificationSettings;
|
||||
$settings->update([
|
||||
'telegram_enabled' => true,
|
||||
'telegram_token' => 'test-token',
|
||||
'telegram_chat_id' => '123456',
|
||||
]);
|
||||
|
||||
Livewire::test(TelegramNotification::class)
|
||||
->call('sendTestNotification')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot update telegram notification settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(TelegramNotification::class)
|
||||
->call('syncData', true)
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('admin can update telegram notification settings', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->telegramNotificationSettings;
|
||||
|
||||
expect($this->admin->can('update', $settings))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Email ---
|
||||
|
||||
test('member cannot send test email notification', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->emailNotificationSettings;
|
||||
$settings->update([
|
||||
'smtp_enabled' => true,
|
||||
'smtp_host' => 'localhost',
|
||||
'smtp_port' => 587,
|
||||
'smtp_from_address' => 'test@test.com',
|
||||
'smtp_from_name' => 'Test',
|
||||
]);
|
||||
|
||||
Livewire::test(EmailNotification::class)
|
||||
->call('sendTestEmail')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot update email notification settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(EmailNotification::class)
|
||||
->call('syncData', true)
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('member cannot copy instance email settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(EmailNotification::class)
|
||||
->call('copyFromInstanceSettings')
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('admin can update email notification settings', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->emailNotificationSettings;
|
||||
|
||||
expect($this->admin->can('update', $settings))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Pushover ---
|
||||
|
||||
test('member cannot send test notification on pushover', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->pushoverNotificationSettings;
|
||||
$settings->update([
|
||||
'pushover_enabled' => true,
|
||||
'pushover_app_token' => 'test-token',
|
||||
'pushover_user_key' => 'test-user-key',
|
||||
]);
|
||||
|
||||
Livewire::test(PushoverNotification::class)
|
||||
->call('sendTestNotification')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot update pushover notification settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(PushoverNotification::class)
|
||||
->call('syncData', true)
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('admin can update pushover notification settings', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->pushoverNotificationSettings;
|
||||
|
||||
expect($this->admin->can('update', $settings))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Webhook ---
|
||||
|
||||
test('member cannot send test notification on webhook', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->webhookNotificationSettings;
|
||||
$settings->update([
|
||||
'webhook_enabled' => true,
|
||||
'webhook_url' => 'https://example.com/webhook',
|
||||
]);
|
||||
|
||||
Livewire::test(WebhookNotification::class)
|
||||
->call('sendTestNotification')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot update webhook notification settings', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(WebhookNotification::class)
|
||||
->call('syncData', true)
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('admin can update webhook notification settings', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = $this->team->webhookNotificationSettings;
|
||||
|
||||
expect($this->admin->can('update', $settings))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Send test policy checks ---
|
||||
|
||||
test('admin can send test on all notification channels', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect($this->admin->can('sendTest', $this->team->discordNotificationSettings))->toBeTrue();
|
||||
expect($this->admin->can('sendTest', $this->team->slackNotificationSettings))->toBeTrue();
|
||||
expect($this->admin->can('sendTest', $this->team->telegramNotificationSettings))->toBeTrue();
|
||||
expect($this->admin->can('sendTest', $this->team->emailNotificationSettings))->toBeTrue();
|
||||
expect($this->admin->can('sendTest', $this->team->pushoverNotificationSettings))->toBeTrue();
|
||||
expect($this->admin->can('sendTest', $this->team->webhookNotificationSettings))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot send test on any notification channel', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect($this->member->can('sendTest', $this->team->discordNotificationSettings))->toBeFalse();
|
||||
expect($this->member->can('sendTest', $this->team->slackNotificationSettings))->toBeFalse();
|
||||
expect($this->member->can('sendTest', $this->team->telegramNotificationSettings))->toBeFalse();
|
||||
expect($this->member->can('sendTest', $this->team->emailNotificationSettings))->toBeFalse();
|
||||
expect($this->member->can('sendTest', $this->team->pushoverNotificationSettings))->toBeFalse();
|
||||
expect($this->member->can('sendTest', $this->team->webhookNotificationSettings))->toBeFalse();
|
||||
});
|
||||
248
tests/Feature/ResourceHeadingAuthorizationTest.php
Normal file
248
tests/Feature/ResourceHeadingAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Project\Application\Heading as ApplicationHeading;
|
||||
use App\Livewire\Project\Service\Heading as ServiceHeading;
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
$keyId = DB::table('private_keys')->insertGetId([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Key',
|
||||
'private_key' => 'test-key',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $keyId,
|
||||
]);
|
||||
|
||||
$this->server->settings()->update([
|
||||
'is_reachable' => true,
|
||||
'is_usable' => true,
|
||||
]);
|
||||
|
||||
StandaloneDocker::withoutEvents(function () {
|
||||
$this->destination = StandaloneDocker::firstOrCreate(
|
||||
['server_id' => $this->server->id, 'network' => 'coolify'],
|
||||
['uuid' => (string) Str::uuid(), 'name' => 'test-docker']
|
||||
);
|
||||
});
|
||||
|
||||
$this->project = Project::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$this->environment = $this->project->environments()->first();
|
||||
|
||||
$this->application = Application::factory()->create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test App',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
'status' => 'running',
|
||||
]);
|
||||
|
||||
$this->database = StandalonePostgresql::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test DB',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'testdb',
|
||||
'image' => 'postgres:15',
|
||||
'status' => 'running',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
]);
|
||||
|
||||
$this->service = Service::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Service',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
'docker_compose_raw' => 'version: "3"',
|
||||
]);
|
||||
|
||||
$this->serviceParams = [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_uuid' => $this->environment->uuid,
|
||||
'service_uuid' => $this->service->uuid,
|
||||
];
|
||||
|
||||
$this->databaseUrl = "/project/{$this->project->uuid}/environment/{$this->environment->uuid}/database/{$this->database->uuid}";
|
||||
});
|
||||
|
||||
// --- Application Heading ---
|
||||
|
||||
test('admin sees deploy controls for application', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->assertSee('Redeploy')
|
||||
->assertSee('Restart')
|
||||
->assertSee('Stop');
|
||||
});
|
||||
|
||||
test('member cannot call deploy on application', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->call('deploy')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot call restart on application', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->call('restart')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot call stop on application', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->call('stop')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member does not see terminal link for application', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->assertDontSee('Terminal');
|
||||
});
|
||||
|
||||
test('admin sees terminal link for application', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ApplicationHeading::class, ['application' => $this->application])
|
||||
->assertSee('Terminal');
|
||||
});
|
||||
|
||||
// --- Database Heading (via page route for rendering, policy checks for actions) ---
|
||||
|
||||
test('admin can access database page', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get($this->databaseUrl)->assertSuccessful();
|
||||
});
|
||||
|
||||
test('member can access database page', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get($this->databaseUrl)->assertSuccessful();
|
||||
});
|
||||
|
||||
test('admin can manage database', function () {
|
||||
expect($this->admin->can('manage', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage database', function () {
|
||||
expect($this->member->can('manage', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can access terminal for database', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('canAccessTerminal'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot access terminal for database', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('canAccessTerminal'))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Service Heading ---
|
||||
|
||||
test('admin sees service deploy button', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->assertSee('Deploy');
|
||||
});
|
||||
|
||||
test('member cannot call stop on service', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->call('stop')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member does not see terminal link for service', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->assertDontSee('Terminal');
|
||||
});
|
||||
|
||||
test('admin sees terminal link for service', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->assertSee('Terminal');
|
||||
});
|
||||
112
tests/Feature/SecurityPageAuthorizationTest.php
Normal file
112
tests/Feature/SecurityPageAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Notifications\Discord as DiscordNotification;
|
||||
use App\Livewire\Security\PrivateKey\Index as PrivateKeyIndex;
|
||||
use App\Livewire\Team\Member\Index as TeamMemberIndex;
|
||||
use App\Models\DiscordNotificationSettings;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
// Create a private key for the team (bypass model validation via DB insert)
|
||||
DB::table('private_keys')->insert([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Team SSH Key',
|
||||
'description' => 'Key for testing',
|
||||
'private_key' => 'test-key-content',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
});
|
||||
|
||||
// --- Private Key Index ---
|
||||
|
||||
test('admin sees add and cleanup buttons on private key page', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(PrivateKeyIndex::class)
|
||||
->assertSee('+ Add')
|
||||
->assertSee('Delete unused SSH Keys');
|
||||
});
|
||||
|
||||
test('member does not see add or cleanup buttons on private key page', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(PrivateKeyIndex::class)
|
||||
->assertDontSee('+ Add')
|
||||
->assertDontSee('Delete unused SSH Keys');
|
||||
});
|
||||
|
||||
test('member can view private key names on index page', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
// Member can see key names (view is allowed for same-team members)
|
||||
// but cannot see create/delete buttons (tested separately above)
|
||||
Livewire::test(PrivateKeyIndex::class)
|
||||
->assertSee('Team SSH Key');
|
||||
});
|
||||
|
||||
test('member cannot call cleanup unused keys', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(PrivateKeyIndex::class)
|
||||
->call('cleanupUnusedKeys')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Team Member Index (Invitations) ---
|
||||
|
||||
test('admin sees invite section on team members page', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(TeamMemberIndex::class)
|
||||
->assertSee('Invite New Member');
|
||||
});
|
||||
|
||||
test('member does not see invite section on team members page', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(TeamMemberIndex::class)
|
||||
->assertDontSee('Invite New Member');
|
||||
});
|
||||
|
||||
// --- Notification Settings (Discord as representative) ---
|
||||
|
||||
test('member cannot send test notification on discord', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$settings = DiscordNotificationSettings::where('team_id', $this->team->id)->first();
|
||||
$settings->update([
|
||||
'discord_enabled' => true,
|
||||
'discord_webhook_url' => 'https://discord.com/api/webhooks/test',
|
||||
]);
|
||||
|
||||
Livewire::test(DiscordNotification::class)
|
||||
->call('sendTestNotification')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
222
tests/Feature/ServerAuthorizationTest.php
Normal file
222
tests/Feature/ServerAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Server\Navbar as ServerNavbar;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
$keyId = DB::table('private_keys')->insertGetId([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Key',
|
||||
'private_key' => 'test-key',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $keyId,
|
||||
]);
|
||||
|
||||
$this->server->settings()->update([
|
||||
'is_reachable' => true,
|
||||
'is_usable' => true,
|
||||
]);
|
||||
});
|
||||
|
||||
// --- Server Policy: update ---
|
||||
|
||||
test('admin can update server', function () {
|
||||
expect($this->admin->can('update', $this->server))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update server', function () {
|
||||
expect($this->member->can('update', $this->server))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Server Policy: delete ---
|
||||
|
||||
test('admin can delete server', function () {
|
||||
expect($this->admin->can('delete', $this->server))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete server', function () {
|
||||
expect($this->member->can('delete', $this->server))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Server Policy: view ---
|
||||
|
||||
test('admin can view server', function () {
|
||||
expect($this->admin->can('view', $this->server))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member can view server', function () {
|
||||
expect($this->member->can('view', $this->server))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Server Policy: manageProxy ---
|
||||
|
||||
test('admin can manage proxy', function () {
|
||||
expect($this->admin->can('manageProxy', $this->server))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage proxy', function () {
|
||||
expect($this->member->can('manageProxy', $this->server))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Server Policy: manageSentinel ---
|
||||
|
||||
test('admin can manage sentinel', function () {
|
||||
expect($this->admin->can('manageSentinel', $this->server))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage sentinel', function () {
|
||||
expect($this->member->can('manageSentinel', $this->server))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Server Policy: manageCaCertificate ---
|
||||
|
||||
test('admin can manage CA certificate', function () {
|
||||
expect($this->admin->can('manageCaCertificate', $this->server))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage CA certificate', function () {
|
||||
expect($this->member->can('manageCaCertificate', $this->server))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Server Policy: viewSecurity ---
|
||||
|
||||
test('admin can view security', function () {
|
||||
expect($this->admin->can('viewSecurity', $this->server))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot view security', function () {
|
||||
expect($this->member->can('viewSecurity', $this->server))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Server Policy: create ---
|
||||
|
||||
test('admin can create server', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', Server::class))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot create server', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('create', Server::class))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Cross-team isolation ---
|
||||
|
||||
test('user from different team cannot view server', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('view', $this->server))->toBeFalse();
|
||||
});
|
||||
|
||||
test('user from different team cannot update server', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('update', $this->server))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Navbar Livewire component actions ---
|
||||
|
||||
test('member cannot restart proxy via navbar', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServerNavbar::class, ['server' => $this->server])
|
||||
->call('restart')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot check proxy via navbar', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServerNavbar::class, ['server' => $this->server])
|
||||
->call('checkProxy')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot start proxy via navbar', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServerNavbar::class, ['server' => $this->server])
|
||||
->call('startProxy')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot stop proxy via navbar', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServerNavbar::class, ['server' => $this->server])
|
||||
->call('stop')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Terminal access gate ---
|
||||
|
||||
test('admin can access terminal', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('canAccessTerminal'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot access terminal', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('canAccessTerminal'))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Server page access ---
|
||||
|
||||
test('admin can access server page', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get("/server/{$this->server->uuid}")->assertSuccessful();
|
||||
});
|
||||
|
||||
test('member can access server page', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->get("/server/{$this->server->uuid}")->assertSuccessful();
|
||||
});
|
||||
|
||||
test('unauthenticated user cannot access server page', function () {
|
||||
$this->get("/server/{$this->server->uuid}")->assertRedirect('/login');
|
||||
});
|
||||
242
tests/Feature/ServiceConfigAuthorizationTest.php
Normal file
242
tests/Feature/ServiceConfigAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Project\Service\Heading as ServiceHeading;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
$keyId = DB::table('private_keys')->insertGetId([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Key',
|
||||
'private_key' => 'test-key',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $keyId,
|
||||
]);
|
||||
|
||||
StandaloneDocker::withoutEvents(function () {
|
||||
$this->destination = StandaloneDocker::firstOrCreate(
|
||||
['server_id' => $this->server->id, 'network' => 'coolify'],
|
||||
['uuid' => (string) Str::uuid(), 'name' => 'test-docker']
|
||||
);
|
||||
});
|
||||
|
||||
$this->project = Project::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$this->environment = $this->project->environments()->first();
|
||||
|
||||
$this->service = Service::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Service',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
'docker_compose_raw' => 'version: "3"',
|
||||
]);
|
||||
|
||||
$this->serviceParams = [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_uuid' => $this->environment->uuid,
|
||||
'service_uuid' => $this->service->uuid,
|
||||
];
|
||||
});
|
||||
|
||||
// --- Service Policy: view ---
|
||||
|
||||
test('admin can view service', function () {
|
||||
expect($this->admin->can('view', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member can view service', function () {
|
||||
expect($this->member->can('view', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Service Policy: update ---
|
||||
|
||||
test('admin can update service', function () {
|
||||
expect($this->admin->can('update', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update service', function () {
|
||||
expect($this->member->can('update', $this->service))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Service Policy: delete ---
|
||||
|
||||
test('admin can delete service', function () {
|
||||
expect($this->admin->can('delete', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete service', function () {
|
||||
expect($this->member->can('delete', $this->service))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Service Policy: deploy ---
|
||||
|
||||
test('admin can deploy service', function () {
|
||||
expect($this->admin->can('deploy', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot deploy service', function () {
|
||||
expect($this->member->can('deploy', $this->service))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Service Policy: stop ---
|
||||
|
||||
test('admin can stop service', function () {
|
||||
expect($this->admin->can('stop', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot stop service', function () {
|
||||
expect($this->member->can('stop', $this->service))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Service Policy: manageEnvironment ---
|
||||
|
||||
test('admin can manage service environment variables', function () {
|
||||
expect($this->admin->can('manageEnvironment', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage service environment variables', function () {
|
||||
expect($this->member->can('manageEnvironment', $this->service))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Service Heading Livewire actions ---
|
||||
|
||||
test('member cannot call start on service heading', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->call('start')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot call stop on service heading', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->call('stop')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot call restart on service heading', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->call('restart')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot call force deploy on service heading', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->call('forceDeploy')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Service Heading visibility ---
|
||||
|
||||
test('member does not see terminal link for service', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->assertDontSee('Terminal');
|
||||
});
|
||||
|
||||
test('admin sees terminal link for service', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->assertSee('Terminal');
|
||||
});
|
||||
|
||||
test('admin sees deploy button for service', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(ServiceHeading::class, [
|
||||
'service' => $this->service,
|
||||
'parameters' => $this->serviceParams,
|
||||
'query' => [],
|
||||
])
|
||||
->assertSee('Deploy');
|
||||
});
|
||||
|
||||
// --- Cross-team isolation ---
|
||||
|
||||
test('user from different team cannot view service', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('view', $this->service))->toBeFalse();
|
||||
});
|
||||
|
||||
test('user from different team cannot update service', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'admin']);
|
||||
|
||||
expect($otherUser->can('update', $this->service))->toBeFalse();
|
||||
});
|
||||
256
tests/Feature/SharedResourceAuthorizationTest.php
Normal file
256
tests/Feature/SharedResourceAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Project\Shared\Danger;
|
||||
use App\Livewire\Project\Shared\Tags;
|
||||
use App\Livewire\Project\Shared\Webhooks;
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
|
||||
$keyId = DB::table('private_keys')->insertGetId([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Key',
|
||||
'private_key' => 'test-key',
|
||||
'team_id' => $this->team->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $keyId,
|
||||
]);
|
||||
|
||||
StandaloneDocker::withoutEvents(function () {
|
||||
$this->destination = StandaloneDocker::firstOrCreate(
|
||||
['server_id' => $this->server->id, 'network' => 'coolify'],
|
||||
['uuid' => (string) Str::uuid(), 'name' => 'test-docker']
|
||||
);
|
||||
});
|
||||
|
||||
$this->project = Project::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$this->environment = $this->project->environments()->first();
|
||||
|
||||
$this->application = Application::factory()->create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test App',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
'status' => 'running',
|
||||
]);
|
||||
|
||||
$this->database = StandalonePostgresql::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test DB',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'testdb',
|
||||
'image' => 'postgres:15',
|
||||
'status' => 'running',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
]);
|
||||
|
||||
$this->service = Service::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Service',
|
||||
'environment_id' => $this->environment->id,
|
||||
'destination_id' => $this->destination->id,
|
||||
'destination_type' => $this->destination->getMorphClass(),
|
||||
'docker_compose_raw' => 'version: "3"',
|
||||
]);
|
||||
});
|
||||
|
||||
// --- Danger Zone: Application ---
|
||||
|
||||
test('admin can delete application', function () {
|
||||
expect($this->admin->can('delete', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete application via danger zone', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(Danger::class, ['resource' => $this->application])
|
||||
->call('delete', 'password')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Danger Zone: Database ---
|
||||
|
||||
test('admin can delete database', function () {
|
||||
expect($this->admin->can('delete', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete database via danger zone', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(Danger::class, ['resource' => $this->database])
|
||||
->call('delete', 'password')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Danger Zone: Service ---
|
||||
|
||||
test('admin can delete service', function () {
|
||||
expect($this->admin->can('delete', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete service via danger zone', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(Danger::class, ['resource' => $this->service])
|
||||
->call('delete', 'password')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Tags: Application ---
|
||||
|
||||
test('member cannot add tag to application', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(Tags::class, ['resource' => $this->application])
|
||||
->set('newTags', 'test-tag')
|
||||
->call('submit')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('admin can add tag to application', function () {
|
||||
expect($this->admin->can('update', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Webhooks: Application ---
|
||||
|
||||
test('member cannot update application webhooks', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(Webhooks::class, ['resource' => $this->application])
|
||||
->call('submit')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('admin can update application webhooks', function () {
|
||||
expect($this->admin->can('update', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Resource Limits (policy checks, mount requires full resource data) ---
|
||||
|
||||
test('member cannot update application resource limits', function () {
|
||||
expect($this->member->can('update', $this->application))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can update application resource limits', function () {
|
||||
expect($this->admin->can('update', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update database resource limits', function () {
|
||||
expect($this->member->can('update', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
test('member cannot update service resource limits', function () {
|
||||
expect($this->member->can('update', $this->service))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Environment Variables (Policy checks) ---
|
||||
|
||||
test('admin can manage application environment variables', function () {
|
||||
expect($this->admin->can('manageEnvironment', $this->application))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage application environment variables', function () {
|
||||
expect($this->member->can('manageEnvironment', $this->application))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can manage database environment variables', function () {
|
||||
expect($this->admin->can('manageEnvironment', $this->database))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage database environment variables', function () {
|
||||
expect($this->member->can('manageEnvironment', $this->database))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can manage service environment variables', function () {
|
||||
expect($this->admin->can('manageEnvironment', $this->service))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage service environment variables', function () {
|
||||
expect($this->member->can('manageEnvironment', $this->service))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Project Policy ---
|
||||
|
||||
test('admin can update project', function () {
|
||||
expect($this->admin->can('update', $this->project))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update project', function () {
|
||||
expect($this->member->can('update', $this->project))->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can delete project', function () {
|
||||
expect($this->admin->can('delete', $this->project))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete project', function () {
|
||||
expect($this->member->can('delete', $this->project))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Environment Policy ---
|
||||
|
||||
test('admin can delete environment', function () {
|
||||
expect($this->admin->can('delete', $this->environment))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete environment', function () {
|
||||
expect($this->member->can('delete', $this->environment))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Team Policy ---
|
||||
|
||||
test('admin can update team', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('update', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update team', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('update', $this->team))->toBeFalse();
|
||||
});
|
||||
207
tests/Feature/TeamAuthorizationTest.php
Normal file
207
tests/Feature/TeamAuthorizationTest.php
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
use App\Livewire\Team\Index as TeamIndex;
|
||||
use App\Livewire\Team\Member as TeamMember;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::updateOrCreate(['id' => 0]);
|
||||
|
||||
$this->team = Team::factory()->create(['personal_team' => false]);
|
||||
|
||||
$this->owner = User::factory()->create();
|
||||
$this->owner->teams()->attach($this->team, ['role' => 'owner']);
|
||||
|
||||
$this->admin = User::factory()->create();
|
||||
$this->admin->teams()->attach($this->team, ['role' => 'admin']);
|
||||
|
||||
$this->member = User::factory()->create();
|
||||
$this->member->teams()->attach($this->team, ['role' => 'member']);
|
||||
});
|
||||
|
||||
// --- Team Policy: update ---
|
||||
|
||||
test('owner can update team', function () {
|
||||
$this->actingAs($this->owner);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('update', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('admin can update team', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('update', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot update team', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('update', $this->team))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Team Policy: delete ---
|
||||
|
||||
test('owner can delete team', function () {
|
||||
$this->actingAs($this->owner);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('delete', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('admin can delete team', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('delete', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot delete team', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('delete', $this->team))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Team Policy: manageMembers ---
|
||||
|
||||
test('owner can manage team members', function () {
|
||||
$this->actingAs($this->owner);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('manageMembers', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('admin can manage team members', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('manageMembers', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage team members', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('manageMembers', $this->team))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Team Policy: manageInvitations ---
|
||||
|
||||
test('owner can manage invitations', function () {
|
||||
$this->actingAs($this->owner);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('manageInvitations', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('admin can manage invitations', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('manageInvitations', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
test('member cannot manage invitations', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('manageInvitations', $this->team))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Team Index Livewire: update ---
|
||||
|
||||
test('member cannot submit team settings via policy', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('update', $this->team))->toBeFalse();
|
||||
});
|
||||
|
||||
// --- Team Index Livewire: delete ---
|
||||
|
||||
test('member cannot delete team via index', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(TeamIndex::class)
|
||||
->call('delete', 'password')
|
||||
->assertDispatched('error');
|
||||
|
||||
expect(Team::find($this->team->id))->not->toBeNull();
|
||||
});
|
||||
|
||||
test('admin can delete team via policy', function () {
|
||||
$this->actingAs($this->admin);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('delete', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Team Member Livewire: role changes ---
|
||||
|
||||
test('member cannot change roles via team member component', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(TeamMember::class, ['member' => $this->admin])
|
||||
->call('makeAdmin')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
test('member cannot remove team members', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(TeamMember::class, ['member' => $this->admin])
|
||||
->call('remove')
|
||||
->assertDispatched('error');
|
||||
});
|
||||
|
||||
// --- Invitations & Invite Link (policy checks — views wrapped in @can render empty for members) ---
|
||||
|
||||
test('member cannot manage invitations via policy', function () {
|
||||
$this->actingAs($this->member);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('manageInvitations', $this->team))->toBeFalse();
|
||||
});
|
||||
|
||||
test('owner can manage invitations via policy', function () {
|
||||
$this->actingAs($this->owner);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
expect(auth()->user()->can('manageInvitations', $this->team))->toBeTrue();
|
||||
});
|
||||
|
||||
// --- Cross-team isolation ---
|
||||
|
||||
test('user from different team cannot update team', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($otherUser);
|
||||
session(['currentTeam' => $otherTeam]);
|
||||
|
||||
expect(auth()->user()->can('update', $this->team))->toBeFalse();
|
||||
});
|
||||
|
||||
test('user from different team cannot manage members', function () {
|
||||
$otherTeam = Team::factory()->create();
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUser->teams()->attach($otherTeam, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($otherUser);
|
||||
session(['currentTeam' => $otherTeam]);
|
||||
|
||||
expect(auth()->user()->can('manageMembers', $this->team))->toBeFalse();
|
||||
});
|
||||
|
|
@ -343,9 +343,105 @@ function loginAsMember(): mixed
|
|||
->screenshot();
|
||||
});
|
||||
|
||||
// Note: Application, database, and service deploy/restart/stop controls
|
||||
// are tested via unit policy tests (tests/Unit/Policies/).
|
||||
// Browser tests for these resource pages require complex model chain setup
|
||||
// (Application -> Environment -> Project -> Team, with StandaloneDocker destination)
|
||||
// that causes Livewire mount() to redirect due to model relationship loading issues
|
||||
// in the browser test context.
|
||||
// --- Dashboard: resource links authorization ---
|
||||
|
||||
it('member does not see add resource link on dashboard project cards', function () {
|
||||
$page = loginAsMember();
|
||||
|
||||
$page->assertSee('Projects')
|
||||
->assertSee('My first project')
|
||||
->assertDontSee('+ Add Resource')
|
||||
->screenshot();
|
||||
});
|
||||
|
||||
it('owner sees add resource link on dashboard project cards', function () {
|
||||
loginAndSkipBoarding();
|
||||
|
||||
$page = visit('/dashboard');
|
||||
|
||||
$page->assertSee('Projects')
|
||||
->assertSee('My first project')
|
||||
->assertSee('+ Add Resource')
|
||||
->screenshot();
|
||||
});
|
||||
|
||||
it('member does not see project settings link on dashboard', function () {
|
||||
$page = loginAsMember();
|
||||
|
||||
// The "Settings" link is inside the project card on the dashboard
|
||||
// Member should not see it due to @can('update', $project)
|
||||
$page->assertSee('My first project')
|
||||
->assertDontSee('Settings')
|
||||
->screenshot();
|
||||
});
|
||||
|
||||
// --- Team members page authorization ---
|
||||
|
||||
it('member does not see invite form on team members page', function () {
|
||||
loginAsMember();
|
||||
|
||||
$page = visit('/team/members');
|
||||
|
||||
$page->assertSee('Members')
|
||||
->assertDontSee('Invite New Member')
|
||||
->screenshot();
|
||||
});
|
||||
|
||||
it('owner sees invite form on team members page', function () {
|
||||
loginAndSkipBoarding();
|
||||
|
||||
$page = visit('/team/members');
|
||||
|
||||
$page->assertSee('Members')
|
||||
->assertSee('Invite New Member')
|
||||
->screenshot();
|
||||
});
|
||||
|
||||
it('member does not see role change buttons on team members page', function () {
|
||||
loginAsMember();
|
||||
|
||||
$page = visit('/team/members');
|
||||
|
||||
$page->assertSee('Members')
|
||||
->assertDontSee('To Admin')
|
||||
->assertDontSee('To Owner')
|
||||
->assertDontSee('Remove')
|
||||
->screenshot();
|
||||
});
|
||||
|
||||
// --- Server show page authorization ---
|
||||
|
||||
it('member does not see save button on server show page', function () {
|
||||
loginAsMember();
|
||||
|
||||
$server = Server::first();
|
||||
$page = visit("/server/{$server->uuid}");
|
||||
|
||||
$page->assertSee('General')
|
||||
->assertDontSee('Revalidate server')
|
||||
->screenshot();
|
||||
});
|
||||
|
||||
it('owner sees save button on server show page', function () {
|
||||
loginAndSkipBoarding();
|
||||
|
||||
$server = Server::first();
|
||||
$page = visit("/server/{$server->uuid}");
|
||||
|
||||
$page->assertSee('General')
|
||||
->assertSee('Save')
|
||||
->screenshot();
|
||||
});
|
||||
|
||||
// --- Project show page authorization ---
|
||||
|
||||
it('member does not see delete project button on project page', function () {
|
||||
loginAsMember();
|
||||
|
||||
$project = Project::where('uuid', 'project-1')->first();
|
||||
$page = visit("/project/{$project->uuid}");
|
||||
|
||||
$page->assertSee('Environments')
|
||||
->assertDontSee('Delete Project')
|
||||
->screenshot();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue