Some checks failed
Main Confidence / confidence (push) Failing after 54s
Add `CustomerReviewWorkspace` page for tenant pre-filtered reviews Add customer workspace links to `EvidenceSnapshotResource`, `ReviewPackResource`, and `TenantReviewResource` Implement audit logging for `TenantReviewOpened` and `ReviewPackDownloaded` actions Update ReviewPack download controller to enforce tenant-scoped RBAC Add tests for ReviewPack download authorization and audit logging Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #289
222 lines
9.8 KiB
PHP
222 lines
9.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Filament\Resources\TenantReviewResource;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Support\TenantReviewStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('lists only the latest published review per entitled tenant on the customer review workspace', function (): void {
|
|
$tenantA = Tenant::factory()->create(['name' => 'Alpha Tenant']);
|
|
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'readonly');
|
|
|
|
$tenantB = Tenant::factory()->create([
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
'name' => 'Beta Tenant',
|
|
]);
|
|
createUserWithTenant(tenant: $tenantB, user: $user, role: 'readonly');
|
|
|
|
$tenantDenied = Tenant::factory()->create([
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
'name' => 'Denied Tenant',
|
|
]);
|
|
$otherOwner = User::factory()->create();
|
|
createUserWithTenant(tenant: $tenantDenied, user: $otherOwner, role: 'owner');
|
|
|
|
$tenantASnapshot = seedTenantReviewEvidence($tenantA);
|
|
$tenantBSnapshot = seedTenantReviewEvidence($tenantB);
|
|
$tenantDeniedSnapshot = seedTenantReviewEvidence($tenantDenied);
|
|
|
|
$olderPublishedReview = composeTenantReviewForTest($tenantA, $user, $tenantASnapshot);
|
|
$olderPublishedReview->forceFill([
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'generated_at' => now()->subDays(3),
|
|
'published_at' => now()->subDays(3),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$newerInternalReview = $olderPublishedReview->replicate();
|
|
$newerInternalReview->forceFill([
|
|
'tenant_id' => (int) $tenantA->getKey(),
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
'evidence_snapshot_id' => (int) $tenantASnapshot->getKey(),
|
|
'status' => TenantReviewStatus::Ready->value,
|
|
'generated_at' => now()->subDay(),
|
|
'published_at' => null,
|
|
'published_by_user_id' => null,
|
|
])->save();
|
|
|
|
$latestPublishedReview = $olderPublishedReview->replicate();
|
|
$latestPublishedReview->forceFill([
|
|
'tenant_id' => (int) $tenantA->getKey(),
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
'evidence_snapshot_id' => (int) $tenantASnapshot->getKey(),
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'generated_at' => now(),
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$betaPublishedReview = composeTenantReviewForTest($tenantB, $user, $tenantBSnapshot);
|
|
$betaPublishedReview->forceFill([
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'generated_at' => now()->subHours(2),
|
|
'published_at' => now()->subHours(2),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$deniedPublishedReview = composeTenantReviewForTest($tenantDenied, $otherOwner, $tenantDeniedSnapshot);
|
|
$deniedPublishedReview->forceFill([
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'generated_at' => now()->subHours(3),
|
|
'published_at' => now()->subHours(3),
|
|
'published_by_user_id' => (int) $otherOwner->getKey(),
|
|
])->save();
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext();
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenantA->workspace_id);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(CustomerReviewWorkspace::class)
|
|
->assertCanSeeTableRecords([$tenantA->fresh(), $tenantB->fresh()])
|
|
->assertCanNotSeeTableRecords([$tenantDenied->fresh()])
|
|
->assertSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $latestPublishedReview->fresh()], $tenantA), false)
|
|
->assertSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $betaPublishedReview->fresh()], $tenantB), false)
|
|
->assertDontSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $olderPublishedReview->fresh()], $tenantA), false)
|
|
->assertDontSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $newerInternalReview->fresh()], $tenantA), false)
|
|
->assertDontSee('Publish review')
|
|
->assertDontSee('Refresh review')
|
|
->assertDontSee('Create next review')
|
|
->assertDontSee('Regenerate')
|
|
->assertDontSee('Expire snapshot')
|
|
->assertDontSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $deniedPublishedReview->fresh()], $tenantDenied), false);
|
|
});
|
|
|
|
it('shows entitled tenants without a published review as calm absence rows', function (): void {
|
|
$tenantPublished = Tenant::factory()->create(['name' => 'Published Tenant']);
|
|
[$user, $tenantPublished] = createUserWithTenant(tenant: $tenantPublished, role: 'readonly');
|
|
|
|
$tenantWithoutPublished = Tenant::factory()->create([
|
|
'workspace_id' => (int) $tenantPublished->workspace_id,
|
|
'name' => 'No Published Tenant',
|
|
]);
|
|
createUserWithTenant(tenant: $tenantWithoutPublished, user: $user, role: 'readonly');
|
|
|
|
$publishedSnapshot = seedTenantReviewEvidence($tenantPublished);
|
|
$noPublishedSnapshot = seedTenantReviewEvidence($tenantWithoutPublished);
|
|
|
|
$publishedReview = composeTenantReviewForTest($tenantPublished, $user, $publishedSnapshot);
|
|
$publishedReview->forceFill([
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'published_at' => now()->subHour(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$internalOnlyReview = composeTenantReviewForTest($tenantWithoutPublished, $user, $noPublishedSnapshot);
|
|
$internalOnlyReview->forceFill([
|
|
'status' => TenantReviewStatus::Ready->value,
|
|
'published_at' => null,
|
|
'published_by_user_id' => null,
|
|
])->save();
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext();
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenantPublished->workspace_id);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(CustomerReviewWorkspace::class)
|
|
->assertCanSeeTableRecords([$tenantPublished->fresh(), $tenantWithoutPublished->fresh()])
|
|
->assertSee('No published review')
|
|
->assertSee('No published review available yet')
|
|
->assertDontSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $internalOnlyReview->fresh()], $tenantWithoutPublished), false)
|
|
->assertSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $publishedReview->fresh()], $tenantPublished), false);
|
|
});
|
|
|
|
it('defaults the customer review workspace to the remembered tenant when tenant context is available', function (): void {
|
|
$tenantA = Tenant::factory()->create(['name' => 'Alpha Tenant']);
|
|
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'readonly');
|
|
|
|
$tenantB = Tenant::factory()->create([
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
'name' => 'Beta Tenant',
|
|
]);
|
|
createUserWithTenant(tenant: $tenantB, user: $user, role: 'readonly');
|
|
|
|
$snapshotA = seedTenantReviewEvidence($tenantA);
|
|
$snapshotB = seedTenantReviewEvidence($tenantB);
|
|
|
|
$reviewA = composeTenantReviewForTest($tenantA, $user, $snapshotA);
|
|
$reviewA->forceFill([
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'published_at' => now()->subDay(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$reviewB = composeTenantReviewForTest($tenantB, $user, $snapshotB);
|
|
$reviewB->forceFill([
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext();
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenantA->workspace_id);
|
|
session()->put(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY, [
|
|
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
|
]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(CustomerReviewWorkspace::class)
|
|
->assertSet('tableFilters.tenant_id.value', (string) $tenantB->getKey())
|
|
->filterTable('tenant_id', (string) $tenantB->getKey())
|
|
->assertCanSeeTableRecords([$tenantB->fresh()])
|
|
->assertCanNotSeeTableRecords([$tenantA->fresh()]);
|
|
});
|
|
|
|
it('prefilters the customer review workspace from an explicit tenant query parameter and accepts external tenant identifiers', function (): void {
|
|
$tenantA = Tenant::factory()->create(['name' => 'Alpha Tenant']);
|
|
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'readonly');
|
|
|
|
$tenantB = Tenant::factory()->create([
|
|
'workspace_id' => (int) $tenantA->workspace_id,
|
|
'name' => 'Beta Tenant',
|
|
]);
|
|
createUserWithTenant(tenant: $tenantB, user: $user, role: 'readonly');
|
|
|
|
$snapshotA = seedTenantReviewEvidence($tenantA);
|
|
$snapshotB = seedTenantReviewEvidence($tenantB);
|
|
|
|
$reviewA = composeTenantReviewForTest($tenantA, $user, $snapshotA);
|
|
$reviewA->forceFill([
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$reviewB = composeTenantReviewForTest($tenantB, $user, $snapshotB);
|
|
$reviewB->forceFill([
|
|
'status' => TenantReviewStatus::Published->value,
|
|
'published_at' => now()->subDay(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext();
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenantA->workspace_id);
|
|
|
|
Livewire::withQueryParams(['tenant' => (string) $tenantA->external_id])
|
|
->test(CustomerReviewWorkspace::class)
|
|
->assertSet('tableFilters.tenant_id.value', (string) $tenantA->getKey())
|
|
->filterTable('tenant_id', (string) $tenantA->getKey())
|
|
->assertCanSeeTableRecords([$tenantA->fresh()])
|
|
->assertCanNotSeeTableRecords([$tenantB->fresh()]);
|
|
}); |