instance(GraphClientInterface::class, spec422FailingGraphClient()); $details = app(CoverageV2ReadinessReadModel::class)->inspectDetails($resource, $environment, $user); $summary = $details['typed_render_summary'] ?? null; $encoded = json_encode($summary, JSON_THROW_ON_ERROR); expect($summary)->toBeArray() ->and($summary['resource_type'])->toBe($resourceType) ->and($encoded)->toContain($expectedText) ->and($encoded)->not->toContain('raw_payload') ->and($encoded)->not->toContain('source_endpoint') ->and($encoded)->not->toContain('spec422-feature-secret') ->and($summary['compare_summary']['status'])->toBe('Material changes detected') ->and($summary['compare_summary']['changed'])->toBeTrue() ->and(collect($summary['compare_summary']['changes'])->pluck('label'))->toContain($expectedChange); })->with([ 'transport rule' => [ 'transportRule', ['DisplayName' => 'Spec422 Feature Transport Rule', 'Enabled' => true, 'Actions' => ['RedirectMessageTo' => ['old-security@example.com']]], ['DisplayName' => 'Spec422 Feature Transport Rule', 'Enabled' => true, 'Actions' => ['RedirectMessageTo' => ['security@example.com']], 'clientSecret' => 'spec422-feature-secret'], 'Transport rule', 'security@example.com', 'Actions Redirect Message To', ], 'meeting policy' => [ 'meetingPolicy', ['DisplayName' => 'Spec422 Feature Meeting Policy', 'AllowTranscription' => false], ['DisplayName' => 'Spec422 Feature Meeting Policy', 'AllowTranscription' => true, 'chatContent' => 'spec422-feature-secret'], 'Teams meeting policy', 'Allow Transcription: yes', 'Recording Transcription Allow Transcription', ], ]); it('Spec422 does not render typed summaries for non-renderable latest evidence', function (): void { [$user, $environment, $resource, $latestEvidence] = spec422FeatureEvidencePair( 'meetingPolicy', ['DisplayName' => 'Spec422 Non Renderable Meeting', 'AllowTranscription' => false], ['DisplayName' => 'Spec422 Non Renderable Meeting', 'AllowTranscription' => true], ); $latestEvidence->forceFill(['coverage_level' => CoverageLevel::ContentBacked->value])->save(); $resource->unsetRelation('latestEvidence'); $details = app(CoverageV2ReadinessReadModel::class)->inspectDetails($resource->fresh(), $environment, $user); expect($details['typed_render_summary'] ?? null)->toBeNull(); }); it('Spec422 returns no inspect details when the managed environment scope does not match', function (): void { [$user, $environment, $resource] = spec422FeatureEvidencePair( 'transportRule', ['DisplayName' => 'Spec422 Scoped Rule', 'Enabled' => true], ['DisplayName' => 'Spec422 Scoped Rule', 'Enabled' => false], ); [, $foreignEnvironment] = createMinimalUserWithTenant(role: 'owner'); expect(app(CoverageV2ReadinessReadModel::class)->inspectDetails($resource, $foreignEnvironment, $user))->toBe([]); }); it('Spec422 requires latest evidence to belong to the same provider connection as the resource', function (): void { [$user, $environment, $resource, $latestEvidence] = spec422FeatureEvidencePair( 'appPermissionPolicy', ['DisplayName' => 'Spec422 Provider Policy', 'BlockAppList' => []], ['DisplayName' => 'Spec422 Provider Policy', 'BlockAppList' => [['DisplayName' => 'Consumer App', 'AppId' => 'consumer-app']]], ); $foreignConnection = ProviderConnection::factory()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), ]); $latestEvidence->forceFill(['provider_connection_id' => (int) $foreignConnection->getKey()])->save(); $resource->unsetRelation('latestEvidence'); $details = app(CoverageV2ReadinessReadModel::class)->inspectDetails($resource->fresh(), $environment, $user); expect($details['typed_render_summary'] ?? null)->toBeNull(); }); /** * @return array{0: mixed, 1: mixed, 2: TenantConfigurationResource, 3: TenantConfigurationResourceEvidence} */ function spec422FeatureEvidencePair(string $canonicalType, array $previousPayload, array $latestPayload): array { 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 = spec422FeatureResourceType($canonicalType); $displayName = (string) ($latestPayload['DisplayName'] ?? $latestPayload['DomainName'] ?? 'Spec422 Feature Resource'); $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' => $canonicalType, 'canonical_resource_key' => $canonicalType.':provider_external_id:spec422-feature', 'canonical_key_kind' => CanonicalKeyKind::ProviderExternalId->value, 'source_resource_id' => 'spec422-feature', 'source_display_name' => $displayName, 'source_class' => SourceClass::Tcm->value, 'source_metadata' => [ 'source_contract_key' => 'spec422.synthetic.'.$canonicalType, 'source_endpoint' => '/spec422/synthetic/'.$canonicalType, 'source_version' => 'v1.0', 'registry_source_class' => SourceClass::Tcm->value, 'registry_support_state' => 'out_of_scope', ], 'latest_evidence_state' => EvidenceState::ContentBacked->value, 'latest_identity_state' => IdentityState::Stable->value, 'latest_claim_state' => ClaimState::InternalOnly->value, 'latest_captured_at' => now(), ]); $previousRun = spec422FeatureRun($user, $environment, $connection, $canonicalType, minutesAgo: 5); $latestRun = spec422FeatureRun($user, $environment, $connection, $canonicalType); 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(), 'operation_run_id' => (int) $previousRun->getKey(), 'source_contract_key' => 'spec422.synthetic.'.$canonicalType, 'source_endpoint' => '/spec422/synthetic/'.$canonicalType, 'source_version' => 'v1.0', 'raw_payload' => ['id' => 'spec422-feature'], 'normalized_payload' => $previousPayload, 'payload_hash' => hash('sha256', json_encode($previousPayload, JSON_THROW_ON_ERROR)), 'evidence_state' => EvidenceState::ContentBacked->value, 'coverage_level' => CoverageLevel::Comparable->value, 'capture_outcome' => CaptureOutcome::Captured->value, 'captured_at' => now()->subMinutes(5), ]); $latestEvidence = 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(), 'operation_run_id' => (int) $latestRun->getKey(), 'source_contract_key' => 'spec422.synthetic.'.$canonicalType, 'source_endpoint' => '/spec422/synthetic/'.$canonicalType, 'source_version' => 'v1.0', 'raw_payload' => ['id' => 'spec422-feature', 'secret' => 'spec422-feature-secret'], 'normalized_payload' => $latestPayload, 'payload_hash' => hash('sha256', json_encode($latestPayload, JSON_THROW_ON_ERROR)), 'evidence_state' => EvidenceState::ContentBacked->value, 'coverage_level' => CoverageLevel::Renderable->value, 'capture_outcome' => CaptureOutcome::Captured->value, 'captured_at' => now(), ]); $resource->forceFill([ 'latest_evidence_id' => (int) $latestEvidence->getKey(), 'latest_payload_hash' => (string) $latestEvidence->payload_hash, ])->save(); return [$user, $environment, $resource->refresh(), $latestEvidence]; } function spec422FeatureResourceType(string $canonicalType): TenantConfigurationResourceType { return TenantConfigurationResourceType::query() ->where('canonical_type', $canonicalType) ->where('source_class', SourceClass::Tcm->value) ->firstOrFail(); } function spec422FeatureRun($user, $environment, ProviderConnection $connection, string $canonicalType, int $minutesAgo = 0): OperationRun { $timestamp = $minutesAgo > 0 ? now()->subMinutes($minutesAgo) : now(); return OperationRun::factory()->withUser($user)->forTenant($environment)->create([ 'type' => OperationRunType::TenantConfigurationCapture->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'context' => [ 'target_scope' => [ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'provider_connection_id' => (int) $connection->getKey(), ], 'resource_types' => [$canonicalType], ], 'started_at' => $timestamp, 'completed_at' => $timestamp, ]); } function spec422FailingGraphClient(): GraphClientInterface { return new class implements GraphClientInterface { public function listPolicies(string $policyType, array $options = []): GraphResponse { throw new RuntimeException('Spec422 render path must not call provider clients.'); } public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse { throw new RuntimeException('Spec422 render path must not call provider clients.'); } public function getOrganization(array $options = []): GraphResponse { throw new RuntimeException('Spec422 render path must not call provider clients.'); } public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse { throw new RuntimeException('Spec422 render path must not call provider clients.'); } public function getServicePrincipalPermissions(array $options = []): GraphResponse { throw new RuntimeException('Spec422 render path must not call provider clients.'); } public function request(string $method, string $path, array $options = []): GraphResponse { throw new RuntimeException('Spec422 render path must not call provider clients.'); } }; }