TenantAtlas/apps/platform/tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php
ahmido d644265d30 Spec 203: extract baseline compare strategy (#233)
## Summary
- extract baseline compare orchestration behind an explicit strategy contract and registry
- preserve the current Intune compare path through a dedicated `IntuneCompareStrategy`
- harden compare launch and review surfaces for mixed, unsupported, incomplete, and strategy-failure truth
- add Spec 203 artifacts, focused regression coverage, and future-domain strategy proof tests

## Testing
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Baselines/CompareStrategyRegistryTest.php tests/Unit/Baselines/CompareSubjectResultContractTest.php tests/Feature/Baselines/BaselineCompareStrategySelectionTest.php tests/Feature/Baselines/BaselineComparePreconditionsTest.php tests/Feature/Baselines/BaselineCompareExecutionGuardTest.php tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php tests/Feature/Filament/BaselineCompareLandingStartSurfaceTest.php tests/Feature/Filament/BaselineCompareLandingWhyNoFindingsTest.php tests/Feature/Filament/BaselineCompareMatrixPageTest.php tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

## Notes
- no new Filament panel/provider registration changes
- no global-search resource changes
- no new asset registration or deployment step changes

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #233
2026-04-13 21:17:04 +00:00

183 lines
7.2 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__.'/Support/FakeCompareStrategy.php';
use App\Filament\Pages\BaselineCompareMatrix;
use App\Jobs\CompareBaselineToTenantJob;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Services\Baselines\BaselineCompareService;
use App\Support\Baselines\BaselineReasonCodes;
use App\Support\Baselines\Compare\CompareStrategyRegistry;
use App\Support\Baselines\Compare\IntuneCompareStrategy;
use App\Support\Governance\GovernanceSubjectTaxonomyRegistry;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
use Tests\Feature\Baselines\Support\FakeCompareStrategy;
use Tests\Feature\Baselines\Support\FakeGovernanceSubjectTaxonomyRegistry;
use Tests\Feature\Concerns\BuildsBaselineCompareMatrixFixtures;
uses(RefreshDatabase::class, BuildsBaselineCompareMatrixFixtures::class);
it('fans out compare starts across the visible assigned set without creating a workspace umbrella run', function (): void {
Queue::fake();
$fixture = $this->makeBaselineCompareMatrixFixture();
$readonlyTenant = Tenant::factory()->create([
'workspace_id' => (int) $fixture['workspace']->getKey(),
'name' => 'Readonly Contoso',
]);
$fixture['user']->tenants()->syncWithoutDetaching([
(int) $readonlyTenant->getKey() => ['role' => 'readonly'],
]);
$this->assignTenantToBaselineProfile($fixture['profile'], $readonlyTenant);
$service = app(BaselineCompareService::class);
$existingRunResult = $service->startCompareForProfile(
$fixture['profile'],
$fixture['visibleTenantTwo'],
$fixture['user'],
);
expect($existingRunResult['ok'] ?? false)->toBeTrue();
$result = $service->startCompareForVisibleAssignments($fixture['profile'], $fixture['user']);
expect($result['visibleAssignedTenantCount'])->toBe(3)
->and($result['queuedCount'])->toBe(1)
->and($result['alreadyQueuedCount'])->toBe(1)
->and($result['blockedCount'])->toBe(1);
$launchStates = collect($result['targets'])
->mapWithKeys(static fn (array $target): array => [(int) $target['tenantId'] => (string) $target['launchState']])
->all();
expect($launchStates[(int) $fixture['visibleTenant']->getKey()] ?? null)->toBe('queued')
->and($launchStates[(int) $fixture['visibleTenantTwo']->getKey()] ?? null)->toBe('already_queued')
->and($launchStates[(int) $readonlyTenant->getKey()] ?? null)->toBe('blocked');
Queue::assertPushed(CompareBaselineToTenantJob::class);
$activeRuns = OperationRun::query()
->where('workspace_id', (int) $fixture['workspace']->getKey())
->where('type', 'baseline_compare')
->get();
expect($activeRuns)->toHaveCount(2)
->and($activeRuns->every(static fn (OperationRun $run): bool => $run->tenant_id !== null))->toBeTrue()
->and($activeRuns->every(static fn (OperationRun $run): bool => (string) $run->status === OperationRunStatus::Queued->value))->toBeTrue()
->and($activeRuns->every(static fn (OperationRun $run): bool => (string) $run->outcome === OperationRunOutcome::Pending->value))->toBeTrue()
->and(OperationRun::query()->whereNull('tenant_id')->where('type', 'baseline_compare')->count())->toBe(0);
});
it('runs compare assigned tenants from the matrix page and keeps feedback on tenant-owned runs', function (): void {
Queue::fake();
$fixture = $this->makeBaselineCompareMatrixFixture();
$this->setAdminWorkspaceContext($fixture['user'], $fixture['workspace']);
Livewire::actingAs($fixture['user'])
->test(BaselineCompareMatrix::class, ['record' => $fixture['profile']->getKey()])
->assertActionVisible('compareAssignedTenants')
->assertActionEnabled('compareAssignedTenants')
->callAction('compareAssignedTenants')
->assertStatus(200);
Queue::assertPushed(CompareBaselineToTenantJob::class, 2);
expect(OperationRun::query()
->where('workspace_id', (int) $fixture['workspace']->getKey())
->where('type', 'baseline_compare')
->whereNull('tenant_id')
->count())->toBe(0);
});
it('blocks visible assignment fanout when the baseline scope spans multiple compare strategy families', function (): void {
Queue::fake();
$fixture = $this->makeBaselineCompareMatrixFixture();
app()->instance(GovernanceSubjectTaxonomyRegistry::class, new FakeGovernanceSubjectTaxonomyRegistry);
app()->instance(CompareStrategyRegistry::class, new CompareStrategyRegistry([
app(IntuneCompareStrategy::class),
app(FakeCompareStrategy::class),
]));
$fixture['profile']->update([
'scope_jsonb' => [
'version' => 2,
'entries' => [
[
'domain_key' => 'intune',
'subject_class' => 'policy',
'subject_type_keys' => ['deviceConfiguration'],
'filters' => [],
],
[
'domain_key' => 'entra',
'subject_class' => 'control',
'subject_type_keys' => ['conditionalAccessPolicy'],
'filters' => [],
],
],
],
]);
$result = app(BaselineCompareService::class)->startCompareForVisibleAssignments($fixture['profile'], $fixture['user']);
expect($result['visibleAssignedTenantCount'])->toBe(2)
->and($result['queuedCount'])->toBe(0)
->and($result['alreadyQueuedCount'])->toBe(0)
->and($result['blockedCount'])->toBe(2)
->and(collect($result['targets'])->pluck('reasonCode')->unique()->values()->all())
->toBe([BaselineReasonCodes::COMPARE_MIXED_SCOPE]);
Queue::assertNotPushed(CompareBaselineToTenantJob::class);
});
it('blocks visible assignment fanout when the baseline scope has no compatible compare strategy family', function (): void {
Queue::fake();
$fixture = $this->makeBaselineCompareMatrixFixture();
app()->instance(GovernanceSubjectTaxonomyRegistry::class, new FakeGovernanceSubjectTaxonomyRegistry);
app()->instance(CompareStrategyRegistry::class, new CompareStrategyRegistry([
app(IntuneCompareStrategy::class),
]));
$fixture['profile']->update([
'scope_jsonb' => [
'version' => 2,
'entries' => [
[
'domain_key' => 'entra',
'subject_class' => 'control',
'subject_type_keys' => ['conditionalAccessPolicy'],
'filters' => [],
],
],
],
]);
$result = app(BaselineCompareService::class)->startCompareForVisibleAssignments($fixture['profile'], $fixture['user']);
expect($result['visibleAssignedTenantCount'])->toBe(2)
->and($result['queuedCount'])->toBe(0)
->and($result['alreadyQueuedCount'])->toBe(0)
->and($result['blockedCount'])->toBe(2)
->and(collect($result['targets'])->pluck('reasonCode')->unique()->values()->all())
->toBe([BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE]);
Queue::assertNotPushed(CompareBaselineToTenantJob::class);
});