Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 4m7s
Implemented deterministic Baseline Result Semantics (Spec 383), introducing CompareSubjectResult and CompareEvidenceResult. Replaced generic arrays with strict Data Transfer Objects for Baseline engine output.
151 lines
6.3 KiB
PHP
151 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Livewire\BaselineCompareEvidenceGapTable;
|
|
use App\Support\Badges\TagBadgeCatalog;
|
|
use App\Support\Badges\TagBadgeDomain;
|
|
use App\Support\Baselines\BaselineCompareEvidenceGapDetails;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Features\SupportTesting\Testable;
|
|
use Livewire\Livewire;
|
|
use Tests\Feature\Baselines\Support\BaselineSubjectResolutionFixtures;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
function baselineCompareEvidenceGapTable(Testable $component): Table
|
|
{
|
|
return $component->instance()->getTable();
|
|
}
|
|
|
|
/**
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
function baselineCompareEvidenceGapBuckets(): array
|
|
{
|
|
return BaselineCompareEvidenceGapDetails::fromContext(BaselineSubjectResolutionFixtures::compareContext([
|
|
BaselineSubjectResolutionFixtures::structuredGap([
|
|
'policy_type' => 'deviceConfiguration',
|
|
'subject_key' => 'WiFi-Corp-Profile',
|
|
'subject_class' => 'policy_backed',
|
|
'resolution_path' => 'policy',
|
|
'resolution_outcome' => 'unresolved_ambiguous_identity',
|
|
'reason_code' => 'unresolved_duplicate_candidates',
|
|
'operator_action_category' => 'inspect_subject_mapping',
|
|
]),
|
|
BaselineSubjectResolutionFixtures::structuredGap([
|
|
'policy_type' => 'deviceConfiguration',
|
|
'subject_key' => 'VPN-Always-On',
|
|
'subject_class' => 'policy_backed',
|
|
'resolution_path' => 'policy',
|
|
'resolution_outcome' => 'unresolved_ambiguous_identity',
|
|
'reason_code' => 'unresolved_duplicate_candidates',
|
|
'operator_action_category' => 'inspect_subject_mapping',
|
|
]),
|
|
BaselineSubjectResolutionFixtures::structuredGap([
|
|
'policy_type' => 'deviceCompliancePolicy',
|
|
'subject_key' => 'Windows-Encryption-Required',
|
|
'subject_class' => 'policy_backed',
|
|
'resolution_path' => 'policy',
|
|
'resolution_outcome' => 'unresolved_ambiguous_identity',
|
|
'reason_code' => 'unresolved_duplicate_candidates',
|
|
'operator_action_category' => 'inspect_subject_mapping',
|
|
]),
|
|
BaselineSubjectResolutionFixtures::structuredGap([
|
|
'policy_type' => 'deviceConfiguration',
|
|
'subject_key' => 'Deleted-Policy-ABC',
|
|
'resolution_outcome' => 'missing_local_evidence',
|
|
'reason_code' => 'missing_local_evidence',
|
|
'operator_action_category' => 'run_policy_sync_or_backup',
|
|
]),
|
|
BaselineSubjectResolutionFixtures::structuredGap([
|
|
'policy_type' => 'deviceCompliancePolicy',
|
|
'subject_key' => 'Retired-Compliance-Policy',
|
|
'resolution_outcome' => 'missing_local_evidence',
|
|
'reason_code' => 'missing_local_evidence',
|
|
'operator_action_category' => 'run_policy_sync_or_backup',
|
|
]),
|
|
]))['buckets'];
|
|
}
|
|
|
|
it('uses a Filament table for evidence-gap rows with searchable visible columns', function (): void {
|
|
$component = Livewire::test(BaselineCompareEvidenceGapTable::class, [
|
|
'buckets' => baselineCompareEvidenceGapBuckets(),
|
|
'context' => 'canonical-run',
|
|
]);
|
|
|
|
$table = baselineCompareEvidenceGapTable($component);
|
|
|
|
expect($table->isSearchable())->toBeTrue();
|
|
expect($table->getDefaultSortColumn())->toBe('reason_label');
|
|
expect($table->getColumn('reason_label')?->isSearchable())->toBeTrue();
|
|
expect($table->getColumn('governed_subject_label')?->isSearchable())->toBeTrue();
|
|
expect($table->getColumn('subject_class_label')?->isSearchable())->toBeTrue();
|
|
expect($table->getColumn('resolution_outcome_label')?->isSearchable())->toBeTrue();
|
|
expect($table->getColumn('operator_action_category_label')?->isSearchable())->toBeTrue();
|
|
expect($table->getColumn('subject_key')?->isSearchable())->toBeTrue();
|
|
|
|
$component
|
|
->assertSee('WiFi-Corp-Profile')
|
|
->assertSee('Deleted-Policy-ABC')
|
|
->assertSee('Reason')
|
|
->assertSee('Governed subject')
|
|
->assertSee('Subject class')
|
|
->assertSee('Outcome')
|
|
->assertSee('Next action')
|
|
->assertSee('Subject key')
|
|
->assertSee('Policy-backed')
|
|
->assertSee('Missing local evidence')
|
|
->assertSee('Run policy sync or backup')
|
|
->assertSee(TagBadgeCatalog::spec(TagBadgeDomain::PolicyType, 'deviceConfiguration')->label)
|
|
->assertSee(TagBadgeCatalog::spec(TagBadgeDomain::PolicyType, 'deviceCompliancePolicy')->label);
|
|
});
|
|
|
|
it('filters evidence-gap rows by table search and select filters', function (): void {
|
|
Livewire::test(BaselineCompareEvidenceGapTable::class, [
|
|
'buckets' => baselineCompareEvidenceGapBuckets(),
|
|
'context' => 'tenant-landing',
|
|
])
|
|
->searchTable('Deleted-Policy-ABC')
|
|
->assertSee('Deleted-Policy-ABC')
|
|
->assertDontSee('WiFi-Corp-Profile');
|
|
|
|
Livewire::test(BaselineCompareEvidenceGapTable::class, [
|
|
'buckets' => baselineCompareEvidenceGapBuckets(),
|
|
'context' => 'tenant-landing-filters',
|
|
])
|
|
->filterTable('reason_code', 'missing_local_evidence')
|
|
->assertSee('Retired-Compliance-Policy')
|
|
->assertDontSee('VPN-Always-On')
|
|
->filterTable('policy_type', 'deviceCompliancePolicy')
|
|
->assertSee('Device Compliance')
|
|
->assertSee('Retired-Compliance-Policy')
|
|
->assertDontSee('Deleted-Policy-ABC')
|
|
->filterTable('operator_action_category', 'run_policy_sync_or_backup')
|
|
->assertSee('Run policy sync or backup')
|
|
->filterTable('subject_class', 'policy_backed')
|
|
->assertSee('Policy-backed');
|
|
});
|
|
|
|
it('shows an explicit empty state when only missing-detail buckets exist', function (): void {
|
|
$buckets = BaselineCompareEvidenceGapDetails::fromContext([
|
|
'baseline_compare' => [
|
|
'evidence_gaps' => [
|
|
'count' => 2,
|
|
'by_reason' => [
|
|
'missing_local_evidence' => 2,
|
|
],
|
|
'subjects' => [],
|
|
],
|
|
],
|
|
])['buckets'];
|
|
|
|
Livewire::test(BaselineCompareEvidenceGapTable::class, [
|
|
'buckets' => $buckets,
|
|
'context' => 'legacy-run',
|
|
])
|
|
->assertSee('No recorded gap rows match this view')
|
|
->assertSee('Adjust the current search or filters to review other affected subjects.');
|
|
});
|