TenantAtlas/tests/Feature/Baselines/BaselineSupportCapabilityGuardTest.php
ahmido c17255f854 feat: implement baseline subject resolution semantics (#193)
## Summary
- add the structured subject-resolution foundation for baseline compare and baseline capture, including capability guards, subject descriptors, resolution outcomes, and operator action categories
- persist structured evidence-gap subject records and update compare/capture surfaces, landing projections, and cleanup tooling to use the new contract
- add Spec 163 artifacts and focused Pest coverage for classification, determinism, cleanup, and DB-only rendering

## Validation
- `vendor/bin/sail bin pint --dirty --format agent`
- `vendor/bin/sail artisan test --compact tests/Unit/Support/Baselines tests/Feature/Baselines tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php`

## Notes
- verified locally that a fresh post-restart baseline compare run now writes structured `baseline_compare.evidence_gaps.subjects` records instead of the legacy broad payload shape
- excluded the separate `docs/product/spec-candidates.md` worktree change from this branch commit and PR

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #193
2026-03-25 12:40:45 +00:00

122 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\CaptureBaselineSnapshotJob;
use App\Jobs\CompareBaselineToTenantJob;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineTenantAssignment;
use App\Services\Baselines\BaselineCaptureService;
use App\Services\Baselines\BaselineCompareService;
use App\Support\Baselines\BaselineCaptureMode;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Bus;
uses(RefreshDatabase::class);
function appendBrokenFoundationSupportConfig(): void
{
$foundationTypes = is_array(config('tenantpilot.foundation_types')) ? config('tenantpilot.foundation_types') : [];
$foundationTypes[] = [
'type' => 'brokenFoundation',
'label' => 'Broken Foundation',
'baseline_compare' => [
'supported' => true,
'identity_strategy' => 'external_id',
'resolution' => [
'subject_class' => 'foundation_backed',
'resolution_path' => 'foundation_policy',
'compare_capability' => 'supported',
'capture_capability' => 'supported',
'source_model_expected' => 'inventory',
],
],
];
config()->set('tenantpilot.foundation_types', $foundationTypes);
}
it('persists truthful compare scope capability decisions before dispatching compare work', function (): void {
Bus::fake();
appendBrokenFoundationSupportConfig();
[$user, $tenant] = createUserWithTenant(role: 'owner');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'capture_mode' => BaselineCaptureMode::Opportunistic->value,
'scope_jsonb' => [
'policy_types' => ['deviceConfiguration'],
'foundation_types' => ['roleScopeTag', 'brokenFoundation', 'unknownFoundation'],
],
]);
$snapshot = BaselineSnapshot::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'baseline_profile_id' => (int) $profile->getKey(),
]);
$profile->update(['active_snapshot_id' => (int) $snapshot->getKey()]);
BaselineTenantAssignment::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'tenant_id' => (int) $tenant->getKey(),
'baseline_profile_id' => (int) $profile->getKey(),
]);
$result = app(BaselineCompareService::class)->startCompare($tenant, $user);
expect($result['ok'])->toBeTrue();
$run = $result['run'];
$scope = data_get($run->context, 'effective_scope');
expect(data_get($scope, 'truthful_types'))->toBe(['deviceConfiguration', 'roleScopeTag'])
->and(data_get($scope, 'limited_types'))->toBe(['roleScopeTag'])
->and(data_get($scope, 'all_types'))->toBe(['brokenFoundation', 'deviceConfiguration', 'roleScopeTag'])
->and(data_get($scope, 'unsupported_types'))->toBe(['brokenFoundation'])
->and(data_get($scope, 'invalid_support_types'))->toBe(['brokenFoundation'])
->and(data_get($scope, 'capabilities.deviceConfiguration.support_mode'))->toBe('supported')
->and(data_get($scope, 'capabilities.roleScopeTag.support_mode'))->toBe('limited')
->and(data_get($scope, 'capabilities.brokenFoundation.support_mode'))->toBe('invalid_support_config')
->and(data_get($scope, 'capabilities.unknownFoundation.support_mode'))->toBeNull();
Bus::assertDispatched(CompareBaselineToTenantJob::class);
});
it('persists the same truthful scope capability decisions before dispatching capture work', function (): void {
Bus::fake();
appendBrokenFoundationSupportConfig();
[$user, $tenant] = createUserWithTenant(role: 'owner');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'capture_mode' => BaselineCaptureMode::Opportunistic->value,
'scope_jsonb' => [
'policy_types' => ['deviceConfiguration'],
'foundation_types' => ['roleScopeTag', 'brokenFoundation', 'unknownFoundation'],
],
]);
$result = app(BaselineCaptureService::class)->startCapture($profile, $tenant, $user);
expect($result['ok'])->toBeTrue();
$run = $result['run'];
$scope = data_get($run->context, 'effective_scope');
expect(data_get($scope, 'truthful_types'))->toBe(['deviceConfiguration', 'roleScopeTag'])
->and(data_get($scope, 'limited_types'))->toBe(['roleScopeTag'])
->and(data_get($scope, 'all_types'))->toBe(['brokenFoundation', 'deviceConfiguration', 'roleScopeTag'])
->and(data_get($scope, 'unsupported_types'))->toBe(['brokenFoundation'])
->and(data_get($scope, 'invalid_support_types'))->toBe(['brokenFoundation'])
->and(data_get($scope, 'capabilities.deviceConfiguration.support_mode'))->toBe('supported')
->and(data_get($scope, 'capabilities.roleScopeTag.support_mode'))->toBe('limited')
->and(data_get($scope, 'capabilities.brokenFoundation.support_mode'))->toBe('invalid_support_config')
->and(data_get($scope, 'capabilities.unknownFoundation.support_mode'))->toBeNull();
Bus::assertDispatched(CaptureBaselineSnapshotJob::class);
});