syncDefaults(); [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); [, $otherEnvironment] = createMinimalUserWithTenant(role: 'owner'); $foreignConnection = ProviderConnection::factory()->withCredential()->create([ 'workspace_id' => (int) $otherEnvironment->workspace_id, 'managed_environment_id' => (int) $otherEnvironment->getKey(), ]); $graph = spec426ProviderScopeGraphClient(); app()->instance(GraphClientInterface::class, $graph); $run = 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) $foreignConnection->getKey(), ], 'resource_types' => ['acceptedDomain', 'appPermissionPolicy', 'meetingPolicy', 'transportRule'], 'required_capability' => 'evidence.manage', ], ]); expect(fn () => app(GenericContentEvidenceCaptureService::class)->capture( tenant: $environment, providerConnection: $foreignConnection, operationRun: $run, canonicalTypes: ['acceptedDomain', 'appPermissionPolicy', 'meetingPolicy', 'transportRule'], ))->toThrow(InvalidArgumentException::class, 'Provider connection does not belong to the managed environment scope.'); expect($graph->calls)->toBe([]) ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec426 reuses existing start authorization for selected Exchange and Teams capture requests', function (): void { Queue::fake(); app(ResourceTypeRegistry::class)->syncDefaults(); [$owner, $environment] = createMinimalUserWithTenant(role: 'owner'); [$readonly] = createMinimalUserWithTenant(tenant: $environment, role: 'readonly', workspaceRole: 'readonly'); $outsider = User::factory()->create(); $connection = ProviderConnection::factory()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), ]); [, $otherEnvironment] = createMinimalUserWithTenant(role: 'owner'); $foreignConnection = ProviderConnection::factory()->create([ 'workspace_id' => (int) $otherEnvironment->workspace_id, 'managed_environment_id' => (int) $otherEnvironment->getKey(), ]); expect(fn () => app(StartTenantConfigurationCapture::class)->start( $environment, $connection, $outsider, ['acceptedDomain', 'appPermissionPolicy', 'meetingPolicy', 'transportRule'], ))->toThrow(NotFoundHttpException::class) ->and(fn () => app(StartTenantConfigurationCapture::class)->start( $environment, $connection, $readonly, ['acceptedDomain', 'appPermissionPolicy', 'meetingPolicy', 'transportRule'], ))->toThrow(AuthorizationException::class) ->and(fn () => app(StartTenantConfigurationCapture::class)->start( $environment, $foreignConnection, $owner, ['acceptedDomain', 'appPermissionPolicy', 'meetingPolicy', 'transportRule'], ))->toThrow(NotFoundHttpException::class); Queue::assertNothingPushed(); }); function spec426ProviderScopeGraphClient(): GraphClientInterface { return new class implements GraphClientInterface { public array $calls = []; public function listPolicies(string $policyType, array $options = []): GraphResponse { $this->calls[] = ['policy_type' => $policyType, 'options' => $options]; return new GraphResponse(true, []); } 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); } }; }