TenantAtlas/apps/platform/tests/Feature/Providers/ProviderCapabilityEvaluationTest.php
ahmido 9374260ae1 feat: add Exchange PowerShell invocation gate (#498)
## Summary
- Adds the Spec 431 Exchange PowerShell invocation gate and operation registration slice.
- Introduces trusted provider operation start handling and read-only Exchange PowerShell runner abstractions.
- Updates provider capability/readiness evaluation and coverage for Exchange PowerShell invocation safety.

## Verification
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Providers/ProviderCapabilityEvaluationTest.php tests/Feature/Providers/ProviderOperationCapabilityGateTest.php tests/Feature/TenantConfiguration/Spec431ExchangePowerShellInvocationGateTest.php tests/Unit/Providers/ProviderCapabilityRegistryTest.php tests/Unit/Providers/ProviderOperationStartGateTest.php tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellCommandAllowlistTest.php tests/Unit/Support/TenantConfiguration/Spec431ExchangePowerShellOperationRegistrationTest.php tests/Unit/Verification/ManagedEnvironmentPermissionCapabilityMappingTest.php`
- Result: 80 passed, 685 assertions.

## Product Surface / Ops
- Rendered UI surface changed: N/A.
- Filament/Livewire surface changed: N/A.
- Deployment impact: config/runtime service changes only; no migrations, queues, or assets added.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #498
2026-07-07 15:23:29 +00:00

212 lines
9.2 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentPermission;
use App\Models\ProviderConnection;
use App\Support\Providers\Capabilities\ProviderCapabilityEvaluator;
use App\Support\Providers\Capabilities\ProviderCapabilityStatus;
use App\Support\Providers\ProviderReasonCodes;
use App\Support\Verification\ManagedEnvironmentPermissionCheckClusters;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
if (! function_exists('spec283ConfiguredPermissionRows')) {
function spec283ConfiguredPermissionRows(): array
{
return array_merge(
config('intune_permissions.permissions', []),
config('entra_permissions.permissions', []),
);
}
}
if (! function_exists('spec283SeedRequirementRows')) {
function spec283SeedRequirementRows(ManagedEnvironment $tenant, array $requirementKeys, array $missingKeys = [], array $errorKeys = []): void
{
$connection = ProviderConnection::query()
->where('managed_environment_id', (int) $tenant->getKey())
->where('provider', 'microsoft')
->orderByDesc('is_default')
->orderByDesc('id')
->first();
$details = ['source' => 'spec-283-test'];
if ($connection instanceof ProviderConnection) {
$details = array_merge($details, [
'workspace_id' => is_numeric($connection->workspace_id) ? (int) $connection->workspace_id : null,
'managed_environment_id' => (int) $connection->managed_environment_id,
'provider' => (string) $connection->provider,
'provider_connection_id' => (int) $connection->getKey(),
]);
}
foreach (spec283ConfiguredPermissionRows() as $permission) {
if (! is_array($permission)) {
continue;
}
$mappedRequirementKeys = ManagedEnvironmentPermissionCheckClusters::requirementKeysForPermissionRow($permission);
if (array_intersect($requirementKeys, $mappedRequirementKeys) === []) {
continue;
}
$permissionKey = (string) ($permission['key'] ?? '');
ManagedEnvironmentPermission::query()->updateOrCreate(
[
'managed_environment_id' => (int) $tenant->getKey(),
'permission_key' => $permissionKey,
'workspace_id' => (int) $tenant->workspace_id,
],
[
'status' => in_array($permissionKey, $errorKeys, true)
? 'error'
: (in_array($permissionKey, $missingKeys, true) ? 'missing' : 'granted'),
'details' => $details,
'last_checked_at' => now(),
],
);
}
}
}
if (! function_exists('spec283SeedPermissionRow')) {
function spec283SeedPermissionRow(ManagedEnvironment $tenant, ProviderConnection $connection, string $permissionKey, string $status = 'granted'): void
{
ManagedEnvironmentPermission::query()->updateOrCreate(
[
'managed_environment_id' => (int) $tenant->getKey(),
'permission_key' => $permissionKey,
'workspace_id' => (int) $tenant->workspace_id,
],
[
'status' => $status,
'details' => [
'source' => 'spec-283-test',
'workspace_id' => (int) $connection->workspace_id,
'managed_environment_id' => (int) $connection->managed_environment_id,
'provider' => (string) $connection->provider,
'provider_connection_id' => (int) $connection->getKey(),
],
'last_checked_at' => now(),
],
);
}
}
it('evaluates supported provider capabilities from stored permission evidence', function (): void {
$tenant = ManagedEnvironment::factory()->create([
'managed_environment_id' => '11111111-1111-1111-1111-111111111111',
]);
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'entra_tenant_id' => '11111111-1111-1111-1111-111111111111',
'provider' => 'microsoft',
'verification_status' => 'healthy',
]);
spec283SeedRequirementRows($tenant, ['permissions.intune_configuration', 'permissions.intune_apps']);
$result = app(ProviderCapabilityEvaluator::class)->evaluate($tenant, $connection, 'inventory_read');
expect($result->status)->toBe(ProviderCapabilityStatus::Supported)
->and($result->missingRequirementKeys)->toBe([])
->and($result->blocksExecution())->toBeFalse();
});
it('does not support grouped provider capabilities from partial stored permission evidence', function (): void {
$tenant = ManagedEnvironment::factory()->create([
'managed_environment_id' => '11111111-1111-1111-1111-111111111112',
]);
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'entra_tenant_id' => '11111111-1111-1111-1111-111111111112',
'provider' => 'microsoft',
'verification_status' => 'healthy',
]);
spec283SeedPermissionRow($tenant, $connection, 'DeviceManagementConfiguration.Read.All');
$result = app(ProviderCapabilityEvaluator::class)->evaluate($tenant, $connection, 'inventory_read');
expect($result->status)->toBe(ProviderCapabilityStatus::Missing)
->and($result->blocksExecution())->toBeTrue()
->and($result->missingRequirementKeys)->toContain('permissions.intune_configuration')
->and($result->missingRequirementKeys)->toContain('permissions.intune_apps');
});
it('returns capability-first missing and blocked states', function (): void {
$tenant = ManagedEnvironment::factory()->create([
'managed_environment_id' => '22222222-2222-2222-2222-222222222222',
]);
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'entra_tenant_id' => '22222222-2222-2222-2222-222222222222',
'provider' => 'microsoft',
'verification_status' => 'healthy',
]);
$missing = app(ProviderCapabilityEvaluator::class)->evaluate($tenant, $connection, 'directory_groups_read');
expect($missing->status)->toBe(ProviderCapabilityStatus::Missing)
->and($missing->reasonCode)->toBe(ProviderReasonCodes::ProviderPermissionMissing)
->and($missing->missingRequirementKeys)->toContain('permissions.directory_groups');
$connection->forceFill(['is_enabled' => false])->save();
$blocked = app(ProviderCapabilityEvaluator::class)->evaluate($tenant, $connection->fresh(), 'directory_groups_read');
expect($blocked->status)->toBe(ProviderCapabilityStatus::Blocked)
->and($blocked->reasonCode)->toBe(ProviderReasonCodes::ProviderConnectionInvalid);
});
it('keeps provider capabilities unknown when provider health verification is stale despite fresh permission rows', function (): void {
$tenant = ManagedEnvironment::factory()->create([
'managed_environment_id' => '22222222-2222-2222-2222-222222222223',
]);
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'entra_tenant_id' => '22222222-2222-2222-2222-222222222223',
'provider' => 'microsoft',
'verification_status' => 'healthy',
'last_health_check_at' => now()->subDays(31),
]);
spec283SeedRequirementRows($tenant, ['permissions.directory_groups']);
$result = app(ProviderCapabilityEvaluator::class)->evaluate($tenant, $connection, 'directory_groups_read');
expect($result->status)->toBe(ProviderCapabilityStatus::Unknown)
->and($result->reasonCode)->toBe(ProviderReasonCodes::ProviderPermissionRefreshFailed)
->and($result->missingRequirementKeys)->toContain('permissions.directory_groups')
->and($result->blocksExecution())->toBeTrue();
});
it('treats admin consent as the provider connection check prerequisite', function (): void {
$tenant = ManagedEnvironment::factory()->create([
'managed_environment_id' => '33333333-3333-3333-3333-333333333333',
]);
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'entra_tenant_id' => '33333333-3333-3333-3333-333333333333',
'provider' => 'microsoft',
'consent_status' => 'required',
]);
$result = app(ProviderCapabilityEvaluator::class)->evaluate($tenant, $connection, 'provider_connection_check');
expect($result->status)->toBe(ProviderCapabilityStatus::Missing)
->and($result->reasonCode)->toBe(ProviderReasonCodes::ProviderConsentMissing)
->and($result->providerRequirementKeys)->toBe(['permissions.admin_consent']);
});