TenantAtlas/tests/Browser/Spec177InventoryCoverageTruthSmokeTest.php
ahmido f52d52540c feat: implement inventory coverage truth (#208)
## 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
2026-04-05 12:35:20 +00:00

269 lines
9.5 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\InventoryCoverage;
use App\Filament\Resources\InventoryItemResource;
use App\Models\InventoryItem;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Support\OperationRunOutcome;
use App\Support\Workspaces\WorkspaceContext;
pest()->browser()->timeout(15_000);
function inventoryItemListLivewireScript(string $body): string
{
return <<<JS
const component = window.Livewire?.all().find((entry) => {
if (entry.name === 'App\\Filament\\Resources\\InventoryItemResource\\Pages\\ListInventoryItems') {
return true;
}
const state = entry.canonical ?? {};
return Object.prototype.hasOwnProperty.call(state, 'tableFilters')
&& Object.prototype.hasOwnProperty.call(state, 'tableDeferredFilters')
&& Object.prototype.hasOwnProperty.call(state, 'tableRecordsPerPage')
&& Object.prototype.hasOwnProperty.call(state, 'tableSearch');
});
if (! component) {
throw new Error('ListInventoryItems Livewire component not found.');
}
{$body}
JS;
}
function seedSpec177InventoryCoverageTruthFixtures(Tenant $tenant): OperationRun
{
foreach (range(1, 130) as $index) {
InventoryItem::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'display_name' => sprintf('Browser Inventory %02d', $index),
'policy_type' => 'deviceConfiguration',
'external_id' => sprintf('browser-inventory-%02d', $index),
'platform' => 'windows',
'last_seen_at' => now()->subMinutes($index),
]);
}
InventoryItem::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'display_name' => 'Browser Conditional Access',
'policy_type' => 'conditionalAccessPolicy',
'external_id' => 'browser-conditional-access',
'platform' => 'windows',
'last_seen_at' => now()->subMinutes(31),
]);
return createInventorySyncOperationRunWithCoverage(
$tenant,
[
'conditionalAccessPolicy' => 'succeeded',
'deviceConfiguration' => 'failed',
'roleScopeTag' => 'skipped',
],
['roleScopeTag'],
[
'outcome' => OperationRunOutcome::PartiallySucceeded->value,
'completed_at' => now()->subMinute(),
],
);
}
function seedSpec177InventoryItemFilterPaginationFixtures(Tenant $tenant): void
{
foreach (range(1, 45) as $index) {
InventoryItem::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'display_name' => sprintf('Windows Fresh Device %02d', $index),
'policy_type' => 'deviceConfiguration',
'external_id' => sprintf('windows-fresh-device-%02d', $index),
'platform' => 'windows',
'last_seen_at' => now()->subMinutes($index),
]);
}
foreach (range(1, 3) as $index) {
InventoryItem::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'display_name' => sprintf('Mac Fresh Device %02d', $index),
'policy_type' => 'deviceConfiguration',
'external_id' => sprintf('mac-fresh-device-%02d', $index),
'platform' => 'macOS',
'last_seen_at' => now()->subMinutes(45 + $index),
]);
}
foreach (range(1, 3) as $index) {
InventoryItem::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'display_name' => sprintf('Conditional Access Fresh %02d', $index),
'policy_type' => 'conditionalAccessPolicy',
'external_id' => sprintf('conditional-access-fresh-%02d', $index),
'platform' => 'windows',
'last_seen_at' => now()->subMinutes(48 + $index),
]);
}
foreach (range(46, 55) as $index) {
InventoryItem::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'display_name' => sprintf('Windows Fresh Device %02d', $index),
'policy_type' => 'deviceConfiguration',
'external_id' => sprintf('windows-fresh-device-%02d', $index),
'platform' => 'windows',
'last_seen_at' => now()->subMinutes(6 + $index),
]);
}
foreach (range(1, 3) as $index) {
InventoryItem::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'display_name' => sprintf('Windows Stale Device %02d', $index),
'policy_type' => 'deviceConfiguration',
'external_id' => sprintf('windows-stale-device-%02d', $index),
'platform' => 'windows',
'last_seen_at' => now()->subDays(3)->subMinutes($index),
]);
}
}
it('smokes inventory coverage truth surfaces with filters, pagination, and run drill-through', function (): void {
$tenant = Tenant::factory()->create([
'name' => 'Spec177 Browser Tenant',
'external_id' => 'spec177-browser-tenant',
]);
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$run = seedSpec177InventoryCoverageTruthFixtures($tenant);
$this->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$searchPage = visit(InventoryItemResource::getUrl('index', tenant: $tenant));
$searchPage
->waitForText('Inventory Items')
->assertNoJavaScriptErrors()
->assertSee('Covered types')
->assertSee('Need follow-up')
->assertSee('Coverage basis')
->assertSee('Open basis run')
->assertSee('Run Inventory Sync')
->assertSee('Browser Inventory 01')
->assertDontSee('Browser Inventory 130')
->fill('main input[placeholder="Search"]', 'Browser Inventory 01')
->waitForText('Browser Inventory 01')
->assertNoJavaScriptErrors()
->assertSee('Browser Inventory 01')
->assertDontSee('Browser Inventory 02');
$page = visit(InventoryCoverage::getUrl(tenant: $tenant));
$page
->waitForText('Tenant coverage truth')
->assertNoJavaScriptErrors()
->assertSee('Latest coverage-bearing sync completed')
->assertSee('Open basis run')
->assertSee('Open inventory items')
->fill('input[placeholder="Search by type or label"]', 'Conditional Access')
->waitForText('Conditional Access')
->assertNoJavaScriptErrors()
->assertSee('Conditional Access')
->assertScript("document.querySelector('input[placeholder=\"Search by type or label\"]')?.value === 'Conditional Access'", true)
->click('Open basis run')
->waitForText('Operation #'.(int) $run->getKey())
->assertNoJavaScriptErrors()
->assertRoute('admin.operations.view', ['run' => (int) $run->getKey()])
->assertSee('Inventory sync coverage')
->assertSee('Need follow-up');
$page->script(<<<'JS'
history.back();
JS);
$page
->waitForText('Tenant coverage truth')
->assertNoJavaScriptErrors()
->click('Open inventory items')
->waitForText('Inventory Items')
->assertNoJavaScriptErrors()
->assertSee('Browser Inventory 01');
});
it('smokes inventory item pagination with stable filter combinations', function (): void {
$tenant = Tenant::factory()->create([
'name' => 'Spec177 Browser Filter Tenant',
'external_id' => 'spec177-browser-filter-tenant',
]);
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
seedSpec177InventoryItemFilterPaginationFixtures($tenant);
$this->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$page = visit(InventoryItemResource::getUrl('index', tenant: $tenant));
$page
->waitForText('Inventory Items')
->assertNoJavaScriptErrors()
->assertSee('Windows Fresh Device 01')
->assertDontSee('Windows Fresh Device 30')
->assertDontSee('Mac Fresh Device 01')
->wait(1);
$page->script(inventoryItemListLivewireScript(<<<'JS'
component.$wire.set('tableRecordsPerPage', 50);
JS));
$page
->wait(1)
->waitForText('Mac Fresh Device 01')
->assertNoJavaScriptErrors()
->assertSee('Mac Fresh Device 01')
->assertSee('Conditional Access Fresh 01')
->assertDontSee('Windows Fresh Device 46');
$page->script(inventoryItemListLivewireScript(<<<'JS'
component.$wire.set('tableDeferredFilters.policy_type.value', 'deviceConfiguration');
component.$wire.set('tableDeferredFilters.platform.value', 'windows');
component.$wire.set('tableDeferredFilters.stale.value', '0');
component.$wire.call('applyTableFilters');
JS));
$page
->wait(1)
->waitForText('Active filters')
->waitForText('Windows Fresh Device 46')
->assertNoJavaScriptErrors()
->assertSee('Policy type: Device Configuration')
->assertSee('Platform: Windows')
->assertSee('Freshness: Fresh')
->assertSee('Windows Fresh Device 46')
->assertDontSee('Mac Fresh Device 01')
->assertDontSee('Conditional Access Fresh 01')
->assertDontSee('Windows Stale Device 01');
$page->script(inventoryItemListLivewireScript(<<<'JS'
component.$wire.call('gotoPage', 2);
JS));
$page
->wait(1)
->waitForText('Windows Fresh Device 51')
->assertNoJavaScriptErrors()
->assertSee('Windows Fresh Device 51')
->assertDontSee('Windows Fresh Device 01')
->assertDontSee('Mac Fresh Device 01');
});