syncDefaults(); [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = ProviderConnection::factory()->withCredential()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), ]); app()->instance(GraphClientInterface::class, spec421InspectCaptureGraphClient([ spec421InspectConditionalAccessPayload([ 'grantControls' => ['builtInControls' => ['compliantDevice']], ]), spec421InspectConditionalAccessPayload(), ])); app(GenericContentEvidenceCaptureService::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: spec421InspectRun($user, $environment, $connection), canonicalTypes: ['conditionalAccessPolicy'], ); app(GenericContentEvidenceCaptureService::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: spec421InspectRun($user, $environment, $connection), canonicalTypes: ['conditionalAccessPolicy'], ); $resource = TenantConfigurationResource::query()->sole(); app()->instance(GraphClientInterface::class, spec421FailingGraphClient()); $details = app(CoverageV2ReadinessReadModel::class)->inspectDetails($resource, $environment, $user); $summary = $details['typed_render_summary'] ?? null; expect($summary)->toBeArray() ->and($summary['display_name'])->toBe('Require MFA') ->and($summary['state'])->toBe('enabled') ->and(json_encode($summary, JSON_THROW_ON_ERROR))->toContain('All') ->and(json_encode($summary, JSON_THROW_ON_ERROR))->not->toContain('raw_payload') ->and(json_encode($summary, JSON_THROW_ON_ERROR))->not->toContain('source_endpoint') ->and($summary['compare_summary']['status'])->toBe('Material changes detected') ->and($summary['compare_summary']['changed'])->toBeTrue() ->and(collect($summary['compare_summary']['changes'])->pluck('label')) ->toContain('Grant Controls Built In Controls') ->and(json_encode($summary['compare_summary'], JSON_THROW_ON_ERROR)) ->not->toContain('compliantDevice') ->not->toContain('mfa'); }); it('Spec421 does not render typed summaries for non-content-backed latest evidence', function (): void { app(ResourceTypeRegistry::class)->syncDefaults(); [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = ProviderConnection::factory()->withCredential()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), ]); $resourceType = spec421ConditionalAccessResourceType(); $resource = TenantConfigurationResource::factory()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'provider_connection_id' => (int) $connection->getKey(), 'resource_type_id' => (int) $resourceType->getKey(), 'canonical_type' => 'conditionalAccessPolicy', 'canonical_resource_key' => 'conditionalAccessPolicy:graph_object_id:cap-blocked-421', 'canonical_key_kind' => CanonicalKeyKind::GraphObjectId->value, 'source_resource_id' => 'cap-blocked-421', 'source_display_name' => 'Spec421 blocked Conditional Access policy', 'source_class' => SourceClass::Tcm->value, 'latest_evidence_state' => EvidenceState::PermissionBlocked->value, 'latest_identity_state' => IdentityState::Stable->value, 'latest_claim_state' => ClaimState::ClaimBlocked->value, 'latest_captured_at' => now(), ]); $evidence = TenantConfigurationResourceEvidence::factory()->create([ 'resource_id' => (int) $resource->getKey(), 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'provider_connection_id' => (int) $connection->getKey(), 'resource_type_id' => (int) $resourceType->getKey(), 'source_contract_key' => 'conditionalAccessPolicy', 'source_endpoint' => '/identity/conditionalAccess/policies', 'normalized_payload' => spec421InspectConditionalAccessPayload(), 'evidence_state' => EvidenceState::PermissionBlocked->value, 'coverage_level' => CoverageLevel::Renderable->value, 'capture_outcome' => CaptureOutcome::BlockedPermission->value, 'captured_at' => now(), ]); $resource->forceFill([ 'latest_evidence_id' => (int) $evidence->getKey(), 'latest_payload_hash' => $evidence->payload_hash, ])->save(); $details = app(CoverageV2ReadinessReadModel::class)->inspectDetails($resource, $environment, $user); expect($details['typed_render_summary'] ?? null)->toBeNull(); }); it('Spec421 returns no inspect details when the managed environment scope does not match', function (): void { app(ResourceTypeRegistry::class)->syncDefaults(); [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); [, $foreignEnvironment] = createMinimalUserWithTenant(role: 'owner'); $connection = ProviderConnection::factory()->withCredential()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), ]); app()->instance(GraphClientInterface::class, spec421InspectCaptureGraphClient()); app(GenericContentEvidenceCaptureService::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: spec421InspectRun($user, $environment, $connection), canonicalTypes: ['conditionalAccessPolicy'], ); expect(app(CoverageV2ReadinessReadModel::class)->inspectDetails( TenantConfigurationResource::query()->sole(), $foreignEnvironment, $user, ))->toBe([]); }); function spec421InspectRun($user, $environment, ProviderConnection $connection): OperationRun { return OperationRun::factory()->withUser($user)->forTenant($environment)->create([ 'type' => OperationRunType::TenantConfigurationCapture->value, 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, 'context' => [ 'target_scope' => [ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'provider_connection_id' => (int) $connection->getKey(), ], 'resource_types' => ['conditionalAccessPolicy'], 'required_capability' => 'evidence.manage', ], ]); } function spec421InspectCaptureGraphClient(array $payloads = []): GraphClientInterface { return new class($payloads) implements GraphClientInterface { /** * @param list> $payloads */ public function __construct(private array $payloads) {} public function listPolicies(string $policyType, array $options = []): GraphResponse { return new GraphResponse(true, [array_shift($this->payloads) ?: spec421InspectConditionalAccessPayload()]); } public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse { return new GraphResponse(false, [], 501); } public function getOrganization(array $options = []): GraphResponse { return new GraphResponse(false, [], 501); } public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse { return new GraphResponse(false, [], 501); } public function getServicePrincipalPermissions(array $options = []): GraphResponse { return new GraphResponse(false, [], 501); } public function request(string $method, string $path, array $options = []): GraphResponse { return new GraphResponse(false, [], 501); } }; } function spec421InspectConditionalAccessPayload(array $overrides = []): array { return array_replace_recursive([ 'id' => 'cap-1', 'displayName' => 'Require MFA', 'state' => 'enabled', 'conditions' => [ 'users' => ['includeUsers' => ['All']], 'applications' => ['includeApplications' => ['Office365']], ], 'grantControls' => ['builtInControls' => ['mfa']], ], $overrides); } function spec421ConditionalAccessResourceType() { return \App\Models\TenantConfigurationResourceType::query() ->where('canonical_type', 'conditionalAccessPolicy') ->where('source_class', SourceClass::Tcm->value) ->firstOrFail(); } function spec421FailingGraphClient(): GraphClientInterface { return new class implements GraphClientInterface { public function listPolicies(string $policyType, array $options = []): GraphResponse { throw new RuntimeException('Spec421 render path must not call provider clients.'); } public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse { throw new RuntimeException('Spec421 render path must not call provider clients.'); } public function getOrganization(array $options = []): GraphResponse { throw new RuntimeException('Spec421 render path must not call provider clients.'); } public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse { throw new RuntimeException('Spec421 render path must not call provider clients.'); } public function getServicePrincipalPermissions(array $options = []): GraphResponse { throw new RuntimeException('Spec421 render path must not call provider clients.'); } public function request(string $method, string $path, array $options = []): GraphResponse { throw new RuntimeException('Spec421 render path must not call provider clients.'); } }; }