Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 15s
Added a decision-first section to the Baseline Profile detail page. Includes request caching for summary metrics and corresponding browser/feature tests.
157 lines
6.1 KiB
PHP
157 lines
6.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Resources\BaselineProfileResource;
|
|
use App\Filament\Resources\BaselineProfileResource\Pages\ViewBaselineProfile;
|
|
use App\Models\BaselineProfile;
|
|
use App\Models\BaselineSnapshot;
|
|
use App\Models\BaselineTenantAssignment;
|
|
use App\Support\Baselines\BaselineCaptureMode;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
/**
|
|
* @return array{0: \App\Models\User, 1: \App\Models\ManagedEnvironment, 2: BaselineProfile, 3: BaselineSnapshot}
|
|
*/
|
|
function spec369ReadyProfile(): array
|
|
{
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
$profile->forceFill([
|
|
'name' => 'Spec369 Ready Baseline',
|
|
'capture_mode' => BaselineCaptureMode::Opportunistic->value,
|
|
])->save();
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
return [$user, $tenant, $profile->fresh(), $snapshot->fresh()];
|
|
}
|
|
|
|
it('renders a ready baseline profile with a decision-first summary before technical scope', function (): void {
|
|
[$user, , $profile, $snapshot] = spec369ReadyProfile();
|
|
|
|
$this->actingAs($user)
|
|
->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Decision')
|
|
->assertSee('Ready to compare')
|
|
->assertSee('Current snapshot is consumable and at least one assigned environment is available for compare.')
|
|
->assertSee('Operators can compare assigned environments against the current baseline snapshot.')
|
|
->assertSee('Snapshot #'.$snapshot->getKey().' (Complete)')
|
|
->assertSee('Matches current snapshot')
|
|
->assertSee('Assigned to 1 environment with compare access.')
|
|
->assertSee('Dominant next action')
|
|
->assertSee('Compare now')
|
|
->assertSeeInOrder([
|
|
'Decision',
|
|
'Ready to compare',
|
|
'Baseline truth',
|
|
'Related context',
|
|
'Profile',
|
|
'Scope',
|
|
'Normalization lineage',
|
|
'Metadata',
|
|
]);
|
|
});
|
|
|
|
it('shows capture-oriented decision guidance when no consumable snapshot exists', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$profile = BaselineProfile::factory()->active()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'name' => 'Spec369 Capture Needed Baseline',
|
|
]);
|
|
|
|
BaselineTenantAssignment::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'baseline_profile_id' => (int) $profile->getKey(),
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
$this->actingAs($user)
|
|
->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Decision')
|
|
->assertSee('Capture needed before compare')
|
|
->assertSee('Assignments can be prepared, but compare waits for a complete current snapshot.')
|
|
->assertSee('No complete snapshot')
|
|
->assertSee('No capture attempts yet')
|
|
->assertSee('Assigned to 1 environment with compare access.')
|
|
->assertSee('Capture baseline')
|
|
->assertSeeInOrder(['Decision', 'Capture needed before compare', 'Baseline truth', 'Scope']);
|
|
});
|
|
|
|
it('shows scope repair guidance for invalid governed-subject scope without exposing raw scope json', function (): void {
|
|
[$user, , $profile] = spec369ReadyProfile();
|
|
|
|
DB::table('baseline_profiles')
|
|
->where('id', (int) $profile->getKey())
|
|
->update([
|
|
'scope_jsonb' => json_encode([
|
|
'version' => 2,
|
|
'entries' => [],
|
|
], JSON_THROW_ON_ERROR),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
$profile = $profile->fresh();
|
|
|
|
$this->actingAs($user)
|
|
->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Scope review required')
|
|
->assertSee('Stored governed-subject scope is invalid. Review the profile definition before compare.')
|
|
->assertSee('Capture and compare are blocked until governed-subject scope is reviewed.')
|
|
->assertSee('Edit profile definition')
|
|
->assertDontSee('subject_type_keys')
|
|
->assertDontSee('canonical_scope')
|
|
->assertSeeInOrder(['Decision', 'Scope review required', 'Scope', 'Stored scope is invalid']);
|
|
});
|
|
|
|
it('keeps readonly members on non-mutating decision copy and disabled high-impact actions', function (): void {
|
|
[$owner, $tenant, $profile] = spec369ReadyProfile();
|
|
[$readonly] = createUserWithTenant(tenant: $tenant, user: null, role: 'readonly', workspaceRole: 'readonly');
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
Livewire::actingAs($readonly)
|
|
->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
|
|
->assertSee('Assignment or access required')
|
|
->assertSee('No assigned environment is available to this actor for compare.')
|
|
->assertSee('Open compare matrix')
|
|
->assertActionVisible('capture')
|
|
->assertActionDisabled('capture')
|
|
->assertActionVisible('compareNow')
|
|
->assertActionDisabled('compareNow');
|
|
|
|
$this->actingAs($owner);
|
|
});
|
|
|
|
it('keeps related snapshot and compare matrix context available below the decision', function (): void {
|
|
[$user, , $profile] = spec369ReadyProfile();
|
|
|
|
$this->actingAs($user);
|
|
|
|
$entryKeys = collect(BaselineProfileResource::detailRelatedContextEntries($profile))
|
|
->pluck('key')
|
|
->all();
|
|
|
|
expect($entryKeys)->toContain('baseline_snapshot', 'compare_matrix');
|
|
|
|
$this->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Related context')
|
|
->assertSee('View snapshot')
|
|
->assertSee('Open compare matrix')
|
|
->assertSeeInOrder(['Decision', 'Related context', 'View snapshot', 'Compare matrix', 'Open compare matrix']);
|
|
});
|