Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 2m1s
Implemented the first version of the PDF and HTML renderer for review packs. Added ReviewPackRenderedReportController and related blade views to render reports. Updated EnvironmentReviewResource, ReviewPackResource, ReviewPackService, and routing. Added new tests for the renderer and download actions, and updated UI documentation.
758 lines
28 KiB
PHP
758 lines
28 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Filament\Resources\ReviewPackResource;
|
|
use App\Filament\Resources\ReviewPackResource\Pages\ListReviewPacks;
|
|
use App\Filament\Resources\ReviewPackResource\Pages\ViewReviewPack;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\Finding;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\StoredReport;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Services\Evidence\EvidenceSnapshotService;
|
|
use App\Services\ReviewPackService;
|
|
use App\Services\Settings\SettingsWriter;
|
|
use App\Support\Auth\UiTooltips;
|
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\OperationRunLinks;
|
|
use App\Support\ReviewPackStatus;
|
|
use Filament\Actions\Action;
|
|
use Filament\Actions\ActionGroup;
|
|
use Filament\Facades\Filament;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Livewire\Livewire;
|
|
use Livewire\Features\SupportTesting\Testable;
|
|
use Tests\Feature\Concerns\BuildsGovernanceArtifactTruthFixtures;
|
|
|
|
uses(RefreshDatabase::class, BuildsGovernanceArtifactTruthFixtures::class);
|
|
|
|
beforeEach(function (): void {
|
|
Storage::fake('exports');
|
|
});
|
|
|
|
function getReviewPackEmptyStateAction(Testable $component, string $name): ?Action
|
|
{
|
|
foreach ($component->instance()->getTable()->getEmptyStateActions() as $action) {
|
|
if ($action instanceof Action && $action->getName() === $name) {
|
|
return $action;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getReviewPackHeaderAction(Testable $component, string $name): ?Action
|
|
{
|
|
$instance = $component->instance();
|
|
$instance->cacheInteractsWithHeaderActions();
|
|
|
|
foreach ($instance->getCachedHeaderActions() as $action) {
|
|
if ($action instanceof Action && $action->getName() === $name) {
|
|
return $action;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function seedReviewPackEvidence(ManagedEnvironment $tenant): EvidenceSnapshot
|
|
{
|
|
StoredReport::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE,
|
|
'payload' => [
|
|
'required_count' => 1,
|
|
'granted_count' => 1,
|
|
'permissions' => [
|
|
['key' => 'DeviceManagementConfiguration.ReadWrite.All', 'status' => 'granted'],
|
|
],
|
|
],
|
|
]);
|
|
|
|
StoredReport::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'report_type' => StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES,
|
|
'payload' => [
|
|
'roles' => [
|
|
['displayName' => 'Global Administrator'],
|
|
],
|
|
],
|
|
]);
|
|
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
]);
|
|
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'finding_type' => Finding::FINDING_TYPE_DRIFT,
|
|
]);
|
|
|
|
OperationRun::factory()->forTenant($tenant)->create([
|
|
'type' => OperationRunType::PolicySync->value,
|
|
]);
|
|
|
|
/** @var EvidenceSnapshotService $service */
|
|
$service = app(EvidenceSnapshotService::class);
|
|
$payload = $service->buildSnapshotPayload($tenant);
|
|
|
|
$snapshot = EvidenceSnapshot::query()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'fingerprint' => $payload['fingerprint'],
|
|
'completeness_state' => $payload['completeness'],
|
|
'summary' => $payload['summary'],
|
|
'generated_at' => now(),
|
|
]);
|
|
|
|
foreach ($payload['items'] as $item) {
|
|
$snapshot->items()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'dimension_key' => $item['dimension_key'],
|
|
'state' => $item['state'],
|
|
'required' => $item['required'],
|
|
'source_kind' => $item['source_kind'],
|
|
'source_record_type' => $item['source_record_type'],
|
|
'source_record_id' => $item['source_record_id'],
|
|
'source_fingerprint' => $item['source_fingerprint'],
|
|
'measured_at' => $item['measured_at'],
|
|
'freshness_at' => $item['freshness_at'],
|
|
'summary_payload' => $item['summary_payload'],
|
|
'sort_order' => $item['sort_order'],
|
|
]);
|
|
}
|
|
|
|
return $snapshot->load('items');
|
|
}
|
|
|
|
function createCurrentReviewPackForResourcePreview(ManagedEnvironment $tenant, \App\Models\User $user): array
|
|
{
|
|
$snapshot = seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
|
|
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
|
|
$review->forceFill([
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'environment_review_id' => (int) $review->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'expires_at' => now()->addDay(),
|
|
]);
|
|
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
])->save();
|
|
|
|
return [$review->fresh(), $pack->fresh(['environmentReview'])];
|
|
}
|
|
|
|
// ─── List Page ───────────────────────────────────────────────
|
|
|
|
it('renders the list page for an authorized user', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$this->actingAs($user)
|
|
->get(ReviewPackResource::getUrl('index', tenant: $tenant, panel: 'admin'))
|
|
->assertOk();
|
|
});
|
|
|
|
it('shows review packs belonging to the active tenant', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$otherTenant = ManagedEnvironment::factory()->create();
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $otherTenant->getKey(),
|
|
]);
|
|
|
|
setAdminEnvironmentContext($tenant);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertCanSeeTableRecords([$pack]);
|
|
});
|
|
|
|
it('displays the empty state when no packs exist', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertSee('No review packs yet');
|
|
});
|
|
|
|
// ─── List Page Start CTA Placement ───────────────────────────
|
|
|
|
it('shows generate only in the empty state when no review packs exist', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
$component = Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertTableEmptyStateActionsExistInOrder(['generate_first']);
|
|
|
|
$emptyStateAction = getReviewPackEmptyStateAction($component, 'generate_first');
|
|
$headerAction = getReviewPackHeaderAction($component, 'generate_pack');
|
|
|
|
expect($emptyStateAction)->not->toBeNull()
|
|
->and($emptyStateAction?->getLabel())->toBe('Generate first pack')
|
|
->and($headerAction)->not->toBeNull()
|
|
->and($headerAction?->isVisible())->toBeFalse();
|
|
});
|
|
|
|
it('shows generate in the header once review packs exist', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertActionVisible('generate_pack');
|
|
});
|
|
|
|
it('disables the generate_first action for a readonly user in the empty state', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly');
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
$component = Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertTableEmptyStateActionsExistInOrder(['generate_first']);
|
|
|
|
$emptyStateAction = getReviewPackEmptyStateAction($component, 'generate_first');
|
|
|
|
expect($emptyStateAction)->not->toBeNull()
|
|
->and($emptyStateAction?->isDisabled())->toBeTrue()
|
|
->and($emptyStateAction?->getTooltip())->toBe(UiTooltips::insufficientPermission());
|
|
});
|
|
|
|
it('disables review pack generation actions when the workspace entitlement blocks them', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
app(SettingsWriter::class)->updateWorkspaceSetting(
|
|
actor: $user,
|
|
workspace: $tenant->workspace,
|
|
domain: 'entitlements',
|
|
key: 'review_pack_generation_override_value',
|
|
value: false,
|
|
);
|
|
|
|
app(SettingsWriter::class)->updateWorkspaceSetting(
|
|
actor: $user,
|
|
workspace: $tenant->workspace,
|
|
domain: 'entitlements',
|
|
key: 'review_pack_generation_override_reason',
|
|
value: 'Workspace is temporarily limited to manual reporting only',
|
|
);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
expect(ReviewPackResource::reviewPackGenerationActionTooltip($tenant))
|
|
->toBe('Review pack generation is disabled by workspace override. Reason: Workspace is temporarily limited to manual reporting only');
|
|
|
|
$listPage = Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertTableEmptyStateActionsExistInOrder(['generate_first']);
|
|
|
|
$emptyStateAction = getReviewPackEmptyStateAction($listPage, 'generate_first');
|
|
$headerAction = getReviewPackHeaderAction($listPage, 'generate_pack');
|
|
|
|
expect($emptyStateAction)->not->toBeNull()
|
|
->and($emptyStateAction?->isDisabled())->toBeTrue()
|
|
->and($headerAction)->not->toBeNull()
|
|
->and($headerAction?->isVisible())->toBeFalse();
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ViewReviewPack::class, ['record' => $pack->getKey()])
|
|
->assertActionVisible('regenerate')
|
|
->assertActionDisabled('regenerate');
|
|
});
|
|
|
|
it('reuses an existing ready pack instead of starting a new run', function (): void {
|
|
Queue::fake();
|
|
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$snapshot = seedReviewPackEvidence($tenant);
|
|
|
|
$fingerprint = app(ReviewPackService::class)->computeFingerprint($tenant, [
|
|
'include_pii' => true,
|
|
'include_operations' => true,
|
|
]);
|
|
|
|
ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'fingerprint' => $fingerprint,
|
|
'expires_at' => now()->addDay(),
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
$operationRunsBefore = OperationRun::query()->count();
|
|
$reviewPacksBefore = ReviewPack::query()->count();
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->callAction('generate_pack', [
|
|
'include_pii' => true,
|
|
'include_operations' => true,
|
|
])
|
|
->assertNotified();
|
|
|
|
expect(OperationRun::query()->count())->toBe($operationRunsBefore);
|
|
expect(ReviewPack::query()->count())->toBe($reviewPacksBefore);
|
|
|
|
Queue::assertNothingPushed();
|
|
});
|
|
|
|
it('does not queue generation when no eligible evidence snapshot exists', function (): void {
|
|
Queue::fake();
|
|
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
ReviewPack::factory()->failed()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->callAction('generate_pack', [
|
|
'include_pii' => true,
|
|
'include_operations' => true,
|
|
])
|
|
->assertNotified();
|
|
|
|
expect(ReviewPack::query()->count())->toBe(1);
|
|
Queue::assertNothingPushed();
|
|
});
|
|
|
|
// ─── Table Row Actions ───────────────────────────────────────
|
|
|
|
it('shows the download action for a ready pack', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$filePath = 'review-packs/test.zip';
|
|
Storage::disk('exports')->put($filePath, 'PK-fake');
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'file_path' => $filePath,
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertTableActionVisible('download', $pack);
|
|
});
|
|
|
|
it('keeps expire grouped under More while still allowing owners to expire a ready pack', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$filePath = 'review-packs/expire-test.zip';
|
|
Storage::disk('exports')->put($filePath, 'PK-fake');
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'file_path' => $filePath,
|
|
'file_disk' => 'exports',
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
$livewire = Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertCanSeeTableRecords([$pack]);
|
|
|
|
$table = $livewire->instance()->getTable();
|
|
$rowActions = $table->getActions();
|
|
$primaryRowActionNames = collect($rowActions)
|
|
->reject(static fn ($action): bool => $action instanceof ActionGroup)
|
|
->map(static fn ($action): ?string => $action->getName())
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
|
|
$moreActionNames = collect($moreGroup?->getActions())
|
|
->map(static fn ($action): ?string => $action->getName())
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
expect($primaryRowActionNames)->toBe(['download'])
|
|
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
|
|
->and($moreGroup?->getLabel())->toBe('More')
|
|
->and($moreActionNames)->toEqualCanonicalizing(['expire']);
|
|
|
|
$livewire
|
|
->assertTableActionVisible('expire', $pack)
|
|
->callTableAction('expire', $pack);
|
|
|
|
$pack->refresh();
|
|
expect($pack->status)->toBe(ReviewPackStatus::Expired->value);
|
|
Storage::disk('exports')->assertMissing($filePath);
|
|
});
|
|
|
|
// ─── View Page ───────────────────────────────────────────────
|
|
|
|
it('renders the view page for a ready pack', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$snapshot = seedReviewPackEvidence($tenant);
|
|
$run = OperationRun::factory()->forTenant($tenant)->create([
|
|
'type' => 'environment.review_pack.generate',
|
|
]);
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'summary' => [
|
|
'finding_count' => 5,
|
|
'report_count' => 2,
|
|
'operation_count' => 12,
|
|
'data_freshness' => [
|
|
'permission_posture' => now()->toIso8601String(),
|
|
'entra_admin_roles' => now()->toIso8601String(),
|
|
'findings' => now()->toIso8601String(),
|
|
'hardening' => now()->toIso8601String(),
|
|
],
|
|
'evidence_resolution' => [
|
|
'outcome' => 'resolved',
|
|
'snapshot_id' => (int) $snapshot->getKey(),
|
|
'snapshot_fingerprint' => (string) $snapshot->fingerprint,
|
|
],
|
|
],
|
|
'options' => ['include_pii' => true, 'include_operations' => true],
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->get(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant, panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Outcome summary')
|
|
->assertDontSee('Artifact truth')
|
|
->assertSee('Artifact reference')
|
|
->assertSee('Review pack #'.$pack->getKey())
|
|
->assertSee('Lifecycle')
|
|
->assertSee('Current')
|
|
->assertSee('Retention')
|
|
->assertSee('Retained')
|
|
->assertSee('Publishable')
|
|
->assertSee('#'.$snapshot->getKey())
|
|
->assertSee(OperationRunLinks::view($run, $tenant), false)
|
|
->assertSee('resolved');
|
|
});
|
|
|
|
it('shows blocked publication truth when the source review is no longer publishable', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$snapshot = seedReviewPackEvidence($tenant);
|
|
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
|
|
|
|
$review->update([
|
|
'status' => 'draft',
|
|
'summary' => array_replace_recursive(is_array($review->summary) ? $review->summary : [], [
|
|
'publish_blockers' => ['Review the missing approval note before publication.'],
|
|
]),
|
|
]);
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'environment_review_id' => (int) $review->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'summary' => [
|
|
'review_status' => 'draft',
|
|
'review_completeness_state' => 'complete',
|
|
'evidence_resolution' => [
|
|
'outcome' => 'resolved',
|
|
'snapshot_id' => (int) $snapshot->getKey(),
|
|
'snapshot_fingerprint' => (string) $snapshot->fingerprint,
|
|
],
|
|
],
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->get(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant, panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Publication blocked')
|
|
->assertSee('Open the source review before sharing this pack');
|
|
});
|
|
|
|
it('shows internal-only caveats for packs generated from stale source evidence before download', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$snapshot = seedStaleEnvironmentReviewEvidence($tenant);
|
|
$review = $this->makeArtifactTruthReview(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
snapshot: $snapshot,
|
|
reviewOverrides: [
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
'completeness_state' => 'complete',
|
|
],
|
|
summaryOverrides: [
|
|
'publish_blockers' => [],
|
|
'section_state_counts' => [
|
|
'complete' => 6,
|
|
'partial' => 0,
|
|
'missing' => 0,
|
|
'stale' => 0,
|
|
],
|
|
],
|
|
);
|
|
|
|
$pack = $this->makeArtifactTruthReviewPack(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
snapshot: $snapshot,
|
|
review: $review,
|
|
summaryOverrides: [
|
|
'review_status' => 'published',
|
|
'review_completeness_state' => 'complete',
|
|
],
|
|
);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ListReviewPacks::class)
|
|
->assertTableActionVisible('download', $pack)
|
|
->assertSee('Internal only')
|
|
->assertSee('Refresh the source review before sharing this pack');
|
|
|
|
$this->actingAs($user)
|
|
->get(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant))
|
|
->assertOk()
|
|
->assertSee('Internal only')
|
|
->assertSee('Download')
|
|
->assertSee('Refresh the source review before sharing this pack');
|
|
});
|
|
|
|
it('keeps packs from partial evidence internal only instead of publishable', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$snapshot = seedPartialEnvironmentReviewEvidence($tenant);
|
|
$review = $this->makeArtifactTruthReview(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
snapshot: $snapshot,
|
|
reviewOverrides: [
|
|
'status' => 'ready',
|
|
'completeness_state' => 'complete',
|
|
],
|
|
summaryOverrides: [
|
|
'publish_blockers' => [],
|
|
'section_state_counts' => [
|
|
'complete' => 6,
|
|
'partial' => 0,
|
|
'missing' => 0,
|
|
'stale' => 0,
|
|
],
|
|
],
|
|
);
|
|
|
|
$pack = $this->makeArtifactTruthReviewPack(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
snapshot: $snapshot,
|
|
review: $review,
|
|
summaryOverrides: [
|
|
'review_status' => 'ready',
|
|
'review_completeness_state' => 'complete',
|
|
],
|
|
);
|
|
|
|
$this->actingAs($user)
|
|
->get(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant, panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Internal only')
|
|
->assertSee('Complete the source review before sharing this pack')
|
|
->assertDontSee('Publishable');
|
|
});
|
|
|
|
it('shows download header action on view page for a ready pack', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ViewReviewPack::class, ['record' => $pack->getKey()])
|
|
->assertActionVisible('download');
|
|
});
|
|
|
|
it('shows rendered report header action on view page for the current review-derived pack', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
[, $pack] = createCurrentReviewPackForResourcePreview($tenant, $user);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ViewReviewPack::class, ['record' => $pack->getKey()])
|
|
->assertActionVisible('open_rendered_report')
|
|
->assertActionVisible('download');
|
|
});
|
|
|
|
it('shows regenerate header action on view page', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ViewReviewPack::class, ['record' => $pack->getKey()])
|
|
->assertActionVisible('regenerate');
|
|
});
|
|
|
|
it('hides regenerate and raw pack diagnostics in the customer review pack flow', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly');
|
|
|
|
[, $pack] = createCurrentReviewPackForResourcePreview($tenant, $user);
|
|
$pack->forceFill([
|
|
'sha256' => hash('sha256', 'customer-pack-flow'),
|
|
'fingerprint' => hash('sha256', 'customer-pack-fingerprint'),
|
|
])->save();
|
|
$pack = $pack->fresh(['environmentReview']);
|
|
|
|
$this->actingAs($user)
|
|
->get(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant, panel: 'admin').'?'.http_build_query([
|
|
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
|
|
]))
|
|
->assertOk()
|
|
->assertSee('Outcome summary')
|
|
->assertDontSee('Regenerate')
|
|
->assertDontSee('SHA-256')
|
|
->assertDontSee('Fingerprint')
|
|
->assertDontSee('Include PII')
|
|
->assertDontSee('Include operations');
|
|
|
|
$tenant->makeCurrent();
|
|
Filament::setTenant($tenant, true);
|
|
|
|
Livewire::withQueryParams(['source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE])
|
|
->actingAs($user)
|
|
->test(ViewReviewPack::class, ['record' => $pack->getKey()])
|
|
->assertActionVisible('open_rendered_report')
|
|
->assertActionVisible('download')
|
|
->assertActionDoesNotExist('regenerate');
|
|
});
|
|
|
|
// ─── Non-Member Access ───────────────────────────────────────
|
|
|
|
it('returns 404 for non-members on list page', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
$otherTenant = ManagedEnvironment::factory()->create();
|
|
|
|
[$user] = createUserWithTenant($otherTenant, role: 'owner');
|
|
|
|
$this->actingAs($user)
|
|
->get(ReviewPackResource::getUrl('index', tenant: $tenant, panel: 'admin'))
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('returns 404 for non-members on view page', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
$otherTenant = ManagedEnvironment::factory()->create();
|
|
|
|
[$user] = createUserWithTenant($otherTenant, role: 'owner');
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->get(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant))
|
|
->assertNotFound();
|
|
});
|