TenantAtlas/apps/platform/tests/Unit/ResolutionGuidance/Spec353ProviderReadinessResolutionAdapterTest.php
ahmido d2876af95b feat: provider connections resolution guidance v1 (spec 353) (#424)
Implemented the first version of provider readiness resolution guidance. Added the ProviderReadinessResolutionAdapter, provider readiness guidance card, and updated EnvironmentRequiredPermissions, ProviderConnectionResource, and ListProviderConnections/ViewProviderConnection. Added tests and updated the design coverage matrix.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #424
2026-06-04 22:41:04 +00:00

182 lines
8.3 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Services\Intune\ManagedEnvironmentRequiredPermissionsViewModelBuilder;
use App\Support\ManagedEnvironmentLinks;
use App\Support\OperationRunLinks;
use App\Support\Providers\ProviderConsentStatus;
use App\Support\Providers\ProviderVerificationStatus;
use App\Support\ResolutionGuidance\Adapters\ProviderReadinessResolutionAdapter;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\mock;
uses(RefreshDatabase::class);
beforeEach(function (): void {
config()->set('graph.client_id', 'spec353-platform-client');
config()->set('graph.client_secret', 'spec353-platform-secret');
config()->set('graph.managed_environment_id', 'organizations');
});
function mockSpec353PermissionOverview(array $overview = []): void
{
mock(ManagedEnvironmentRequiredPermissionsViewModelBuilder::class, function ($mock) use ($overview): void {
$mock->shouldReceive('build')->andReturn([
'overview' => array_replace_recursive([
'counts' => [
'missing_application' => 0,
'missing_delegated' => 0,
'present' => 12,
'error' => 0,
],
'freshness' => [
'last_refreshed_at' => now()->toIso8601String(),
'is_stale' => false,
],
], $overview),
]);
});
}
it('returns connection-missing guidance when no default provider connection exists', function (): void {
[, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false);
mockSpec353PermissionOverview();
$case = app(ProviderReadinessResolutionAdapter::class)
->forEnvironment($environment, ProviderReadinessResolutionAdapter::SURFACE_PROVIDER_CONNECTIONS_INDEX);
expect($case['key'])->toBe('provider_readiness.connection_missing')
->and(data_get($case, 'primary_action.url'))->toBe(ManagedEnvironmentLinks::providerConnectionsUrl($environment))
->and((string) data_get($case, 'status'))->toBe(__('localization.provider_guidance.status_blocked'));
});
it('distinguishes admin-consent blockers from missing-permission blockers', function (): void {
[, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false);
$connection = ProviderConnection::factory()->platform()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'is_default' => true,
'consent_status' => ProviderConsentStatus::Required->value,
'verification_status' => ProviderVerificationStatus::Unknown->value,
]);
mockSpec353PermissionOverview([
'counts' => [
'missing_application' => 3,
],
]);
$case = app(ProviderReadinessResolutionAdapter::class)
->forConnection($environment, $connection, ProviderReadinessResolutionAdapter::SURFACE_PROVIDER_CONNECTIONS_VIEW);
expect($case['key'])->toBe('provider_readiness.admin_consent_required')
->and((string) data_get($case, 'primary_action.label'))->toBe(__('localization.provider_guidance.action_open_admin_consent'))
->and((string) data_get($case, 'reason'))->toBe(__('localization.provider_guidance.admin_consent_required_reason'));
});
it('returns required-permissions guidance when application permissions are missing', function (): void {
[, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false);
$connection = ProviderConnection::factory()->platform()->consentGranted()->verifiedHealthy()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'is_default' => true,
]);
mockSpec353PermissionOverview([
'counts' => [
'missing_application' => 2,
'present' => 10,
],
]);
$case = app(ProviderReadinessResolutionAdapter::class)
->forConnection($environment, $connection, ProviderReadinessResolutionAdapter::SURFACE_PROVIDER_CONNECTIONS_VIEW);
expect($case['key'])->toBe('provider_readiness.required_permissions_missing')
->and((string) data_get($case, 'primary_action.label'))->toBe(__('localization.provider_guidance.action_open_required_permissions'))
->and((string) data_get($case, 'reason'))->toBe(__('localization.provider_guidance.required_application_permissions_reason'));
});
it('returns verification-required guidance when verification has not run yet', function (): void {
[, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false);
$connection = ProviderConnection::factory()->platform()->consentGranted()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'is_default' => true,
'verification_status' => ProviderVerificationStatus::Unknown->value,
'last_health_check_at' => null,
]);
mockSpec353PermissionOverview([
'freshness' => [
'last_refreshed_at' => null,
'is_stale' => true,
],
]);
$case = app(ProviderReadinessResolutionAdapter::class)
->forConnection($environment, $connection, ProviderReadinessResolutionAdapter::SURFACE_REQUIRED_PERMISSIONS);
expect($case['key'])->toBe('provider_readiness.verification_required')
->and((string) data_get($case, 'primary_action.action_name'))->toBe('runProviderVerification')
->and((string) data_get($case, 'primary_action.label'))->toBe(__('localization.provider_guidance.action_run_provider_verification'));
});
it('returns verification-failed guidance with an operation link when a failed run exists', function (): void {
[, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false);
$connection = ProviderConnection::factory()->platform()->consentGranted()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'is_default' => true,
'verification_status' => ProviderVerificationStatus::Error->value,
'last_error_message' => 'Graph verification failed for this connection.',
]);
$run = OperationRun::factory()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
'type' => 'provider.connection.check',
'context' => [
'provider_connection_id' => (int) $connection->getKey(),
],
]);
mockSpec353PermissionOverview();
$case = app(ProviderReadinessResolutionAdapter::class)
->forConnection($environment, $connection, ProviderReadinessResolutionAdapter::SURFACE_PROVIDER_CONNECTIONS_VIEW);
expect($case['key'])->toBe('provider_readiness.verification_failed')
->and((string) data_get($case, 'primary_action.url'))->toBe(OperationRunLinks::view($run, $environment))
->and((string) data_get($case, 'reason'))->toContain('Graph verification failed');
});
it('returns a ready case when provider signals are satisfied', function (): void {
[, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false);
$connection = ProviderConnection::factory()->platform()->verifiedHealthy()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'is_default' => true,
'last_health_check_at' => now(),
]);
mockSpec353PermissionOverview();
$case = app(ProviderReadinessResolutionAdapter::class)
->forConnection($environment, $connection, ProviderReadinessResolutionAdapter::SURFACE_PROVIDER_CONNECTIONS_VIEW);
expect($case['key'])->toBe('provider_readiness.ready')
->and((string) data_get($case, 'primary_action.url'))->toBe(ManagedEnvironmentLinks::viewUrl($environment))
->and((string) data_get($case, 'status'))->toBe(__('localization.provider_guidance.status_ready'));
});