## Summary - implement Spec 177 inventory coverage truth across resolver, badges, KPIs, coverage page, and operation run detail surfaces - add repo-native spec artifacts for the feature under `specs/177-inventory-coverage-truth` - add unit, feature, and browser coverage for truth derivation, continuity, and inventory item filter/pagination smoke paths ## Testing - `vendor/bin/sail bin pint --dirty --format agent` - focused Spec 177 browser smoke file passed with 2 tests / 57 assertions - extended inventory-focused test pack passed with 52 tests / 434 assertions Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #208
151 lines
5.7 KiB
PHP
151 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\InventoryItem;
|
|
use App\Models\OperationRun;
|
|
use App\Models\Tenant;
|
|
use App\Support\Inventory\TenantCoverageTruthResolver;
|
|
use App\Support\Inventory\TenantCoverageTypeTruth;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('selects the latest completed coverage-bearing inventory sync run for tenant truth', function (): void {
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
$olderBasis = createInventorySyncOperationRunWithCoverage($tenant, [
|
|
'deviceConfiguration' => ['status' => 'succeeded', 'item_count' => 1],
|
|
], attributes: [
|
|
'completed_at' => now()->subHours(2),
|
|
'outcome' => 'succeeded',
|
|
]);
|
|
|
|
createInventorySyncOperationRun($tenant, [
|
|
'status' => 'completed',
|
|
'outcome' => 'failed',
|
|
'completed_at' => now()->subMinute(),
|
|
]);
|
|
|
|
$latestCoverageBearing = createInventorySyncOperationRunWithCoverage($tenant, [
|
|
'deviceConfiguration' => ['status' => 'failed', 'item_count' => 2, 'error_code' => 'graph_forbidden'],
|
|
], attributes: [
|
|
'completed_at' => now()->subMinutes(5),
|
|
'outcome' => 'failed',
|
|
]);
|
|
|
|
expect(OperationRun::latestCompletedCoverageBearingInventorySyncForTenant((int) $tenant->getKey())?->is($latestCoverageBearing))
|
|
->toBeTrue()
|
|
->and(OperationRun::latestCompletedCoverageBearingInventorySyncForTenant((int) $tenant->getKey())?->is($olderBasis))
|
|
->toBeFalse();
|
|
|
|
$truth = app(TenantCoverageTruthResolver::class)->resolve($tenant);
|
|
|
|
expect($truth->basisRunId())->toBe((int) $latestCoverageBearing->getKey())
|
|
->and($truth->basisRunOutcome())->toBe('failed')
|
|
->and($truth->hasCurrentCoverageResult)->toBeTrue();
|
|
});
|
|
|
|
it('derives per-type coverage truth, observed counts, and deterministic follow-up order', function (): void {
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
InventoryItem::factory()->count(3)->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'policy_type' => 'deviceCompliancePolicy',
|
|
]);
|
|
|
|
InventoryItem::factory()->count(2)->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'policy_type' => 'conditionalAccessPolicy',
|
|
]);
|
|
|
|
InventoryItem::factory()->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'policy_type' => 'deviceConfiguration',
|
|
]);
|
|
|
|
createInventorySyncOperationRunWithCoverage($tenant, [
|
|
'deviceCompliancePolicy' => [
|
|
'status' => 'failed',
|
|
'item_count' => 3,
|
|
'error_code' => 'graph_forbidden',
|
|
],
|
|
'deviceConfiguration' => [
|
|
'status' => 'succeeded',
|
|
'item_count' => 1,
|
|
],
|
|
'roleScopeTag' => [
|
|
'status' => 'skipped',
|
|
'item_count' => 0,
|
|
],
|
|
], foundationTypes: ['roleScopeTag'], attributes: [
|
|
'completed_at' => now(),
|
|
'outcome' => 'partially_succeeded',
|
|
]);
|
|
|
|
$truth = app(TenantCoverageTruthResolver::class)->resolve($tenant);
|
|
|
|
$rowsByType = collect($truth->rows)->keyBy(
|
|
static fn (TenantCoverageTypeTruth $row): string => $row->type,
|
|
);
|
|
|
|
expect($truth->supportedTypeCount)->toBeGreaterThanOrEqual(4)
|
|
->and($truth->succeededTypeCount)->toBe(1)
|
|
->and($truth->failedTypeCount)->toBe(1)
|
|
->and($truth->skippedTypeCount)->toBe(1)
|
|
->and($truth->unknownTypeCount)->toBe($truth->supportedTypeCount - 3)
|
|
->and($truth->followUpTypeCount)->toBe($truth->supportedTypeCount - 1)
|
|
->and($truth->observedItemTotal)->toBe(6);
|
|
|
|
expect($rowsByType['deviceCompliancePolicy'])
|
|
->coverageState->toBe('failed')
|
|
->basisErrorCode->toBe('graph_forbidden')
|
|
->basisItemCount->toBe(3)
|
|
->followUpGuidance->toBe('Review provider consent or permissions, then rerun inventory sync.');
|
|
|
|
expect($rowsByType['conditionalAccessPolicy'])
|
|
->coverageState->toBe('unknown')
|
|
->observedItemCount->toBe(2)
|
|
->followUpGuidance->toBe('No current basis result exists for this type. Run inventory sync to confirm coverage.');
|
|
|
|
expect($rowsByType['deviceConfiguration'])
|
|
->coverageState->toBe('succeeded')
|
|
->basisItemCount->toBe(1)
|
|
->followUpRequired->toBeFalse();
|
|
|
|
expect($rowsByType['roleScopeTag'])
|
|
->coverageState->toBe('skipped')
|
|
->segment->toBe('foundation')
|
|
->followUpGuidance->toBe('Run inventory sync again with the required types selected.');
|
|
|
|
$orderedTypes = array_map(
|
|
static fn (TenantCoverageTypeTruth $row): string => $row->type,
|
|
$truth->rows,
|
|
);
|
|
|
|
expect(array_search('deviceCompliancePolicy', $orderedTypes, true))->toBeLessThan(array_search('conditionalAccessPolicy', $orderedTypes, true))
|
|
->and(array_search('conditionalAccessPolicy', $orderedTypes, true))->toBeLessThan(array_search('roleScopeTag', $orderedTypes, true))
|
|
->and($truth->topPriorityFollowUpRow()?->type)->toBe('deviceCompliancePolicy');
|
|
});
|
|
|
|
it('returns unknown coverage rows when no basis run exists', function (): void {
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
InventoryItem::factory()->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'policy_type' => 'deviceConfiguration',
|
|
]);
|
|
|
|
$truth = app(TenantCoverageTruthResolver::class)->resolve($tenant);
|
|
|
|
expect($truth->hasCurrentCoverageResult)->toBeFalse()
|
|
->and($truth->basisRunId())->toBeNull()
|
|
->and($truth->succeededTypeCount)->toBe(0)
|
|
->and($truth->failedTypeCount)->toBe(0)
|
|
->and($truth->skippedTypeCount)->toBe(0)
|
|
->and($truth->unknownTypeCount)->toBe($truth->supportedTypeCount)
|
|
->and($truth->followUpTypeCount)->toBe($truth->supportedTypeCount)
|
|
->and($truth->observedItemTotal)->toBe(1)
|
|
->and($truth->topPriorityFollowUpRow())->not->toBeNull();
|
|
});
|