instance(); if ($instance->getCachedHeaderActions() === []) { $instance->cacheInteractsWithHeaderActions(); } return $instance->getCachedHeaderActions(); } /** * @return array{0: BaselineProfile, 1: BaselineSnapshot} */ function seedComparableBaselineProfileForTenant(Tenant $tenant, BaselineCaptureMode $captureMode = BaselineCaptureMode::FullContent): array { [$profile, $snapshot] = seedActiveBaselineForTenant($tenant); $profile->forceFill([ 'capture_mode' => $captureMode->value, ])->save(); return [$profile->fresh(), $snapshot]; } it('does not start baseline compare for workspace members missing tenant.sync', function (): void { Queue::fake(); config()->set('tenantpilot.baselines.full_content_capture.enabled', true); [$user, $tenant] = createUserWithTenant(role: 'readonly'); [$profile] = seedComparableBaselineProfileForTenant($tenant); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertActionVisible('compareNow') ->assertActionHasLabel('compareNow', 'Compare now (full content)') ->assertActionDisabled('compareNow') ->callAction('compareNow', data: ['target_tenant_id' => (int) $tenant->getKey()]) ->assertStatus(200); Queue::assertNotPushed(CompareBaselineToTenantJob::class); }); it('starts baseline compare successfully for authorized workspace members', function (): void { Queue::fake(); config()->set('tenantpilot.baselines.full_content_capture.enabled', true); [$user, $tenant] = createUserWithTenant(role: 'owner'); [$profile] = seedComparableBaselineProfileForTenant($tenant); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertActionVisible('compareNow') ->assertActionHasLabel('compareNow', 'Compare now (full content)') ->assertActionEnabled('compareNow') ->callAction('compareNow', data: ['target_tenant_id' => (int) $tenant->getKey()]) ->assertStatus(200); Queue::assertPushed(CompareBaselineToTenantJob::class); $run = OperationRun::query() ->where('tenant_id', (int) $tenant->getKey()) ->where('type', 'baseline_compare') ->latest('id') ->first(); expect($run)->not->toBeNull(); expect($run?->status)->toBe('queued'); }); it('does not start full-content baseline compare when rollout is disabled', function (): void { Queue::fake(); config()->set('tenantpilot.baselines.full_content_capture.enabled', false); [$user, $tenant] = createUserWithTenant(role: 'owner'); [$profile] = seedComparableBaselineProfileForTenant($tenant); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertActionVisible('compareNow') ->assertActionHasLabel('compareNow', 'Compare now (full content)') ->assertActionEnabled('compareNow') ->callAction('compareNow', data: ['target_tenant_id' => (int) $tenant->getKey()]) ->assertNotified('Cannot start comparison') ->assertStatus(200); Queue::assertNotPushed(CompareBaselineToTenantJob::class); expect(OperationRun::query()->where('type', 'baseline_compare')->count())->toBe(0); }); it('shows mixed-strategy compare rejection truth on the workspace start surface', function (): void { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); app()->instance(GovernanceSubjectTaxonomyRegistry::class, new FakeGovernanceSubjectTaxonomyRegistry); app()->instance(CompareStrategyRegistry::class, new CompareStrategyRegistry([ app(IntuneCompareStrategy::class), app(FakeCompareStrategy::class), ])); [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic); $profile->forceFill([ 'workspace_id' => (int) $tenant->workspace_id, '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' => [], ], ], ], ])->save(); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertSee('Mixed strategy scope') ->callAction('compareNow', data: ['target_tenant_id' => (int) $tenant->getKey()]) ->assertNotified('Cannot start comparison') ->assertStatus(200); Queue::assertNotPushed(CompareBaselineToTenantJob::class); expect(OperationRun::query()->where('type', 'baseline_compare')->count())->toBe(0); }); it('moves compare-matrix navigation into related context while keeping compare-assigned-tenants secondary', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); $component = Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertActionExists('compareAssignedTenants', fn (Action $action): bool => $action->getLabel() === 'Compare assigned tenants' && $action->isConfirmationRequired() && str_contains((string) $action->getModalDescription(), 'Simulation only.')); $topLevelActionNames = collect(baselineProfileHeaderActions($component)) ->reject(static fn ($action): bool => $action instanceof ActionGroup) ->filter(static fn ($action): bool => ! method_exists($action, 'isVisible') || $action->isVisible()) ->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null) ->filter() ->values() ->all(); $moreGroup = collect(baselineProfileHeaderActions($component)) ->first(static fn ($action): bool => $action instanceof ActionGroup && $action->isVisible()); $moreActionNames = collect($moreGroup?->getActions() ?? []) ->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null) ->filter() ->values() ->all(); expect($topLevelActionNames)->toBe(['compareNow']) ->and($moreGroup)->toBeInstanceOf(ActionGroup::class) ->and($moreActionNames)->toEqualCanonicalizing(['compareAssignedTenants', 'edit']) ->and(collect(BaselineProfileResource::detailRelatedContextEntries($profile))->pluck('key')->all()) ->toContain('compare_matrix', 'baseline_snapshot'); }); it('keeps compare-assigned-tenants visible but disabled for readonly workspace members after the navigation move', function (): void { [$user, $tenant] = createUserWithTenant(role: 'readonly'); [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertActionVisible('compareAssignedTenants') ->assertActionDisabled('compareAssignedTenants'); expect(collect(BaselineProfileResource::detailRelatedContextEntries($profile))->pluck('key')->all()) ->toContain('compare_matrix'); }); it('shows readiness copy without exposing raw canonical scope json on the compare start surface', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic); $profile->forceFill([ 'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []], ])->save(); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertSee('Support readiness') ->assertSee('Capture: ready. Compare: ready.') ->assertDontSee('subject_type_keys') ->assertDontSee('canonical_scope'); }); it('does not start baseline compare when the stored canonical scope is invalid', function (): void { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic); DB::table('baseline_profiles') ->where('id', (int) $profile->getKey()) ->update([ 'scope_jsonb' => json_encode([ 'version' => 2, 'entries' => [ [ 'domain_key' => 'platform_foundation', 'subject_class' => 'configuration_resource', 'subject_type_keys' => ['intuneRoleAssignment'], 'filters' => [], ], ], ], JSON_THROW_ON_ERROR), 'updated_at' => now(), ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertActionVisible('compareNow') ->callAction('compareNow', data: ['target_tenant_id' => (int) $tenant->getKey()]) ->assertNotified('Cannot start comparison') ->assertStatus(200); Queue::assertNotPushed(CompareBaselineToTenantJob::class); expect(OperationRun::query()->where('type', 'baseline_compare')->count())->toBe(0); });