syncDefaults(); config()->set('graph_contracts.types.conditionalAccessPolicy.volatile_fields', ['modifiedDateTime']); [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = ProviderConnection::factory()->withCredential()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), ]); $graph = spec421PromotionGraphClient(); app()->instance(GraphClientInterface::class, $graph); $result = app(GenericContentEvidenceCaptureService::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: spec421PromotionRun($user, $environment, $connection, [ 'conditionalAccessPolicy', 'securityDefaults', 'application', 'servicePrincipal', 'roleDefinition', 'administrativeUnit', ]), canonicalTypes: [ 'conditionalAccessPolicy', 'securityDefaults', 'application', 'servicePrincipal', 'roleDefinition', 'administrativeUnit', ], ); $outcomes = collect($result['outcomes'])->keyBy('canonical_type'); $resources = TenantConfigurationResource::query()->get()->keyBy('canonical_type'); $evidenceByResourceId = TenantConfigurationResourceEvidence::query()->get()->keyBy('resource_id'); expect($graph->calls)->toBe(['conditionalAccessPolicy', 'securityDefaults']) ->and($resources)->toHaveCount(2) ->and($evidenceByResourceId)->toHaveCount(2) ->and($evidenceByResourceId[$resources['conditionalAccessPolicy']->getKey()]->coverage_level)->toBe(CoverageLevel::Renderable) ->and($evidenceByResourceId[$resources['securityDefaults']->getKey()]->coverage_level)->toBe(CoverageLevel::Renderable) ->and($outcomes['conditionalAccessPolicy']['outcome'])->toBe(CaptureOutcome::Captured->value) ->and($outcomes['securityDefaults']['outcome'])->toBe(CaptureOutcome::Captured->value) ->and($outcomes['application']['outcome'])->toBe(CaptureOutcome::BlockedUnsupported->value) ->and($outcomes['servicePrincipal']['outcome'])->toBe(CaptureOutcome::BlockedUnsupported->value) ->and($outcomes['roleDefinition']['outcome'])->toBe(CaptureOutcome::BlockedUnsupported->value) ->and($outcomes['administrativeUnit']['outcome'])->toBe(CaptureOutcome::BlockedUnsupported->value); }); function spec421PromotionRun($user, $environment, ProviderConnection $connection, array $resourceTypes): 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' => $resourceTypes, 'required_capability' => 'evidence.manage', ], ]); } function spec421PromotionGraphClient(): GraphClientInterface { return new class implements GraphClientInterface { public array $calls = []; public function listPolicies(string $policyType, array $options = []): GraphResponse { $this->calls[] = $policyType; if ($policyType === 'securityDefaults') { return new GraphResponse(true, [ 'id' => 'securityDefaults', 'displayName' => 'Security Defaults', 'isEnabled' => true, ]); } return new GraphResponse(true, [[ 'id' => 'cap-1', 'displayName' => 'Require MFA', 'state' => 'enabled', 'conditions' => ['users' => ['includeUsers' => ['All']]], 'grantControls' => ['builtInControls' => ['mfa']], ]]); } 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); } }; }