TenantAtlas/apps/platform/tests/Feature/TenantConfiguration/Spec426ExchangeTeamsProviderScopeTest.php
ahmido f7d06621a0 feat: implement Exchange Teams evidence identity readiness (#493)
Automated PR for spec 426 exchange teams core evidence identity readiness. Includes service changes and coverage/requirement/spec updates from commit fb4dc20c.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #493
2026-07-03 11:43:11 +00:00

139 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\TenantConfigurationResource;
use App\Models\TenantConfigurationResourceEvidence;
use App\Models\User;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\TenantConfiguration\GenericContentEvidenceCaptureService;
use App\Services\TenantConfiguration\ResourceTypeRegistry;
use App\Services\TenantConfiguration\StartTenantConfigurationCapture;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Facades\Queue;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
it('Spec426 rejects cross-scope Exchange and Teams capture before any provider call', function (): void {
app(ResourceTypeRegistry::class)->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);
}
};
}