292 lines
12 KiB
PHP
292 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Jobs\TenantConfiguration\CaptureTenantConfigurationEvidenceJob;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\TenantConfigurationResource;
|
|
use App\Models\TenantConfigurationResourceEvidence;
|
|
use App\Models\TenantConfigurationResourceType;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
use App\Services\TenantConfiguration\ResourceTypeRegistry;
|
|
use App\Services\TenantConfiguration\StartTenantConfigurationCapture;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\TenantConfiguration\ClaimState;
|
|
use App\Support\TenantConfiguration\EvidenceState;
|
|
use App\Support\TenantConfiguration\IdentityState;
|
|
use App\Support\TenantConfiguration\SourceClass;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
|
it('allows provider connections in the same managed environment and workspace', function (): void {
|
|
Queue::fake();
|
|
|
|
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
|
|
$run = app(StartTenantConfigurationCapture::class)->start($tenant, $connection, $user, [
|
|
'deviceAndAppManagementAssignmentFilter',
|
|
]);
|
|
|
|
expect((int) data_get($run->context, 'target_scope.provider_connection_id'))->toBe((int) $connection->getKey());
|
|
|
|
Queue::assertPushed(CaptureTenantConfigurationEvidenceJob::class);
|
|
});
|
|
|
|
it('rejects provider connections from another managed environment or workspace', function (): void {
|
|
Queue::fake();
|
|
|
|
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
[, $otherTenant] = createMinimalUserWithTenant(role: 'owner');
|
|
|
|
$otherConnection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $otherTenant->workspace_id,
|
|
'managed_environment_id' => (int) $otherTenant->getKey(),
|
|
]);
|
|
|
|
expect(fn () => app(StartTenantConfigurationCapture::class)->start($tenant, $otherConnection, $user))
|
|
->toThrow(NotFoundHttpException::class);
|
|
|
|
Queue::assertNothingPushed();
|
|
});
|
|
|
|
it('enforces provider connection scope at the PostgreSQL constraint layer', function (): void {
|
|
if (DB::getDriverName() !== 'pgsql') {
|
|
test()->markTestSkipped('PostgreSQL composite foreign key constraint coverage.');
|
|
}
|
|
|
|
[, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
[, $otherTenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$resourceType = spec415CaptureResourceType();
|
|
|
|
$foreignConnection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $otherTenant->workspace_id,
|
|
'managed_environment_id' => (int) $otherTenant->getKey(),
|
|
]);
|
|
|
|
expect(fn () => TenantConfigurationResource::query()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'provider_connection_id' => (int) $foreignConnection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'source_class' => SourceClass::Tcm->value,
|
|
'canonical_type' => (string) $resourceType->canonical_type,
|
|
'canonical_resource_key' => 'deviceAndAppManagementAssignmentFilter:foreign-provider',
|
|
'source_resource_id' => 'foreign-provider',
|
|
'source_metadata' => ['fixture' => 'provider-scope'],
|
|
'latest_evidence_state' => EvidenceState::NotCaptured->value,
|
|
'latest_identity_state' => IdentityState::Stable->value,
|
|
'latest_claim_state' => ClaimState::InternalOnly->value,
|
|
]))->toThrow(QueryException::class);
|
|
});
|
|
|
|
it('enforces provider connection workspace/environment binding at the PostgreSQL constraint layer', function (): void {
|
|
if (DB::getDriverName() !== 'pgsql') {
|
|
test()->markTestSkipped('PostgreSQL composite foreign key constraint coverage.');
|
|
}
|
|
|
|
[, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$foreignWorkspace = Workspace::factory()->create();
|
|
|
|
expect(fn () => ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $foreignWorkspace->getKey(),
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]))->toThrow(QueryException::class);
|
|
});
|
|
|
|
it('enforces evidence resource/provider scope at the PostgreSQL constraint layer', function (): void {
|
|
if (DB::getDriverName() !== 'pgsql') {
|
|
test()->markTestSkipped('PostgreSQL composite foreign key constraint coverage.');
|
|
}
|
|
|
|
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$resourceType = spec415CaptureResourceType();
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
$otherScopedConnection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
$resource = spec415CaptureResource($tenant, $connection, $resourceType, 'provider-mismatch');
|
|
$run = spec415CaptureRun($tenant, $user);
|
|
|
|
expect(fn () => DB::table('tenant_configuration_resource_evidence')->insert(
|
|
spec415EvidenceRow($tenant, $resource, $otherScopedConnection, $resourceType, $run),
|
|
))->toThrow(QueryException::class);
|
|
});
|
|
|
|
it('enforces latest evidence pointer scope at the PostgreSQL constraint layer', function (): void {
|
|
if (DB::getDriverName() !== 'pgsql') {
|
|
test()->markTestSkipped('PostgreSQL composite foreign key constraint coverage.');
|
|
}
|
|
|
|
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$resourceType = spec415CaptureResourceType();
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
$sourceResource = spec415CaptureResource($tenant, $connection, $resourceType, 'latest-source');
|
|
$targetResource = spec415CaptureResource($tenant, $connection, $resourceType, 'latest-target');
|
|
$run = spec415CaptureRun($tenant, $user);
|
|
|
|
$sourceEvidence = TenantConfigurationResourceEvidence::factory()->create([
|
|
'resource_id' => (int) $sourceResource->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
]);
|
|
|
|
expect(fn () => $targetResource->forceFill([
|
|
'latest_evidence_id' => (int) $sourceEvidence->getKey(),
|
|
])->save())->toThrow(QueryException::class);
|
|
});
|
|
|
|
it('allows resource deletion after latest evidence is linked at the PostgreSQL constraint layer', function (): void {
|
|
if (DB::getDriverName() !== 'pgsql') {
|
|
test()->markTestSkipped('PostgreSQL composite foreign key lifecycle coverage.');
|
|
}
|
|
|
|
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$resourceType = spec415CaptureResourceType();
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
$resource = spec415CaptureResource($tenant, $connection, $resourceType, 'latest-delete');
|
|
$run = spec415CaptureRun($tenant, $user);
|
|
|
|
$evidence = TenantConfigurationResourceEvidence::factory()->create([
|
|
'resource_id' => (int) $resource->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
]);
|
|
|
|
$resource->forceFill([
|
|
'latest_evidence_id' => (int) $evidence->getKey(),
|
|
])->save();
|
|
|
|
$resource->delete();
|
|
|
|
expect(TenantConfigurationResource::query()->whereKey($resource->getKey())->exists())->toBeFalse()
|
|
->and(TenantConfigurationResourceEvidence::query()->whereKey($evidence->getKey())->exists())->toBeFalse();
|
|
});
|
|
|
|
it('enforces evidence operation run scope at the PostgreSQL constraint layer', function (): void {
|
|
if (DB::getDriverName() !== 'pgsql') {
|
|
test()->markTestSkipped('PostgreSQL composite foreign key constraint coverage.');
|
|
}
|
|
|
|
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
|
|
[, $otherTenant] = createMinimalUserWithTenant(role: 'owner');
|
|
$resourceType = spec415CaptureResourceType();
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
$resource = spec415CaptureResource($tenant, $connection, $resourceType, 'operation-mismatch');
|
|
$foreignRun = OperationRun::factory()->forTenant($otherTenant)->create([
|
|
'type' => OperationRunType::TenantConfigurationCapture->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
]);
|
|
|
|
expect(fn () => DB::table('tenant_configuration_resource_evidence')->insert(
|
|
spec415EvidenceRow($tenant, $resource, $connection, $resourceType, $foreignRun),
|
|
))->toThrow(QueryException::class);
|
|
});
|
|
|
|
function spec415CaptureResourceType(): TenantConfigurationResourceType
|
|
{
|
|
app(ResourceTypeRegistry::class)->syncDefaults();
|
|
|
|
return TenantConfigurationResourceType::query()
|
|
->where('canonical_type', 'deviceAndAppManagementAssignmentFilter')
|
|
->firstOrFail();
|
|
}
|
|
|
|
function spec415CaptureResource(
|
|
ManagedEnvironment $tenant,
|
|
ProviderConnection $connection,
|
|
TenantConfigurationResourceType $resourceType,
|
|
string $suffix,
|
|
): TenantConfigurationResource {
|
|
return TenantConfigurationResource::query()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'source_class' => SourceClass::Tcm->value,
|
|
'canonical_type' => (string) $resourceType->canonical_type,
|
|
'canonical_resource_key' => 'deviceAndAppManagementAssignmentFilter:'.$suffix,
|
|
'source_resource_id' => $suffix,
|
|
'source_metadata' => ['fixture' => 'resource-scope'],
|
|
'latest_evidence_state' => EvidenceState::NotCaptured->value,
|
|
'latest_identity_state' => IdentityState::Stable->value,
|
|
'latest_claim_state' => ClaimState::InternalOnly->value,
|
|
]);
|
|
}
|
|
|
|
function spec415CaptureRun(ManagedEnvironment $tenant, User $user): OperationRun
|
|
{
|
|
return OperationRun::factory()->withUser($user)->forTenant($tenant)->create([
|
|
'type' => OperationRunType::TenantConfigurationCapture->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
]);
|
|
}
|
|
|
|
function spec415EvidenceRow(
|
|
ManagedEnvironment $tenant,
|
|
TenantConfigurationResource $resource,
|
|
ProviderConnection $connection,
|
|
TenantConfigurationResourceType $resourceType,
|
|
OperationRun $run,
|
|
): array {
|
|
$now = now();
|
|
|
|
return [
|
|
'resource_id' => (int) $resource->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'source_contract_key' => 'assignmentFilter',
|
|
'source_endpoint' => '/deviceManagement/assignmentFilters',
|
|
'source_version' => 'v1.0',
|
|
'source_metadata' => json_encode(['fixture' => 'evidence-scope'], JSON_THROW_ON_ERROR),
|
|
'raw_payload' => json_encode(['id' => 'assignment-filter-'.$resource->getKey()], JSON_THROW_ON_ERROR),
|
|
'normalized_payload' => json_encode(['id' => 'assignment-filter-'.$resource->getKey()], JSON_THROW_ON_ERROR),
|
|
'payload_hash' => hash('sha256', 'assignment-filter-'.$resource->getKey()),
|
|
'permission_context' => json_encode(['scopes_granted' => []], JSON_THROW_ON_ERROR),
|
|
'evidence_state' => EvidenceState::ContentBacked->value,
|
|
'coverage_level' => 'content_backed',
|
|
'capture_outcome' => 'captured',
|
|
'captured_at' => $now,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
}
|