## Summary - retrofit the tenant detail recent-operations and verification surfaces to keep one clear primary inspect path per state - keep onboarding workflow actions on the wizard step while moving previous-run and advanced monitoring links into diagnostics-only technical details - add focused spec 172 design artifacts, feature coverage, and a dedicated browser smoke test for the deferred operator surface retrofit ## Testing - `vendor/bin/sail artisan test --compact tests/Browser/Spec172DeferredOperatorSurfacesSmokeTest.php tests/Browser/OnboardingDraftRefreshTest.php tests/Browser/OnboardingDraftVerificationResumeTest.php` ## Notes - base branch: `dev` - branch head: `172-deferred-operator-surfaces-retrofit` - browser smoke pack passed locally after the final changes Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #203
319 lines
12 KiB
PHP
319 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Resources\TenantResource;
|
|
use App\Filament\Resources\TenantResource\Pages\ViewTenant;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\Tenant;
|
|
use App\Models\TenantOnboardingSession;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
use App\Support\OperationRunLinks;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\Verification\VerificationReportWriter;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
|
|
pest()->browser()->timeout(15_000);
|
|
|
|
it('smokes tenant detail with existing operation surfaces and canonical operations routes', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$report = VerificationReportWriter::build('provider.connection.check', [
|
|
[
|
|
'key' => 'provider.connection.check',
|
|
'title' => 'Provider connection preflight',
|
|
'status' => 'fail',
|
|
'severity' => 'critical',
|
|
'blocking' => true,
|
|
'reason_code' => 'provider_connection_missing',
|
|
'message' => 'No provider connection configured.',
|
|
'evidence' => [],
|
|
'next_steps' => [],
|
|
],
|
|
]);
|
|
|
|
$run = OperationRun::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'type' => 'provider.connection.check',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Blocked->value,
|
|
'context' => [
|
|
'target_scope' => [
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
],
|
|
'verification_report' => $report,
|
|
],
|
|
]);
|
|
|
|
$this->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
|
|
]);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
$page = visit(TenantResource::getUrl('view', ['record' => $tenant->getRouteKey()], panel: 'admin'));
|
|
|
|
$page
|
|
->assertNoJavaScriptErrors()
|
|
->assertSee((string) $tenant->name)
|
|
->assertSee('Recent operations')
|
|
->assertSee('Verification report')
|
|
->assertSee(OperationRunLinks::openCollectionLabel())
|
|
->assertSee(OperationRunLinks::collectionScopeDescription())
|
|
->assertSee(OperationRunLinks::openLabel())
|
|
->assertSee(ViewTenant::verificationHeaderActionLabel())
|
|
->assertDontSee('Start verification')
|
|
->click(OperationRunLinks::openCollectionLabel())
|
|
->assertNoJavaScriptErrors()
|
|
->assertRoute('admin.operations.index');
|
|
|
|
visit(OperationRunLinks::tenantlessView($run))
|
|
->assertNoJavaScriptErrors()
|
|
->assertRoute('admin.operations.view', ['run' => (int) $run->getKey()])
|
|
->assertSee(OperationRunLinks::identifier((int) $run->getKey()));
|
|
});
|
|
|
|
it('smokes tenant detail empty verification state without an inline inspect action', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$this->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
|
|
]);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
visit(TenantResource::getUrl('view', ['record' => $tenant->getRouteKey()], panel: 'admin'))
|
|
->assertNoJavaScriptErrors()
|
|
->assertSee('Verification report')
|
|
->assertSee('No verification operation has been started yet.')
|
|
->assertSee('Start verification')
|
|
->assertDontSee(OperationRunLinks::openLabel());
|
|
});
|
|
|
|
it('smokes onboarding verify step without a verification run', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$tenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'status' => Tenant::STATUS_ONBOARDING,
|
|
]);
|
|
$user = User::factory()->create(['name' => 'Spec172 Browser Owner']);
|
|
|
|
createUserWithTenant(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
role: 'owner',
|
|
workspaceRole: 'owner',
|
|
ensureDefaultMicrosoftProviderConnection: false,
|
|
);
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'provider' => 'microsoft',
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'is_default' => true,
|
|
'status' => 'connected',
|
|
]);
|
|
|
|
TenantOnboardingSession::query()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'current_step' => 'verify',
|
|
'state' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
],
|
|
'started_by_user_id' => (int) $user->getKey(),
|
|
'updated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(),
|
|
]);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
visit('/admin/onboarding')
|
|
->assertNoJavaScriptErrors()
|
|
->assertSee('Verify access')
|
|
->assertSee('Run a queued verification check (Operation).')
|
|
->assertSee('Start verification')
|
|
->assertDontSee('Refresh')
|
|
->assertDontSee(OperationRunLinks::openLabel());
|
|
});
|
|
|
|
it('smokes onboarding active verification state with refresh and current-run inspection', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$tenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'status' => Tenant::STATUS_ONBOARDING,
|
|
]);
|
|
$user = User::factory()->create(['name' => 'Spec172 Active Browser Owner']);
|
|
|
|
createUserWithTenant(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
role: 'owner',
|
|
workspaceRole: 'owner',
|
|
ensureDefaultMicrosoftProviderConnection: false,
|
|
);
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'provider' => 'microsoft',
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'is_default' => true,
|
|
'status' => 'connected',
|
|
]);
|
|
|
|
$run = OperationRun::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'type' => 'provider.connection.check',
|
|
'status' => OperationRunStatus::Running->value,
|
|
'outcome' => OperationRunOutcome::Pending->value,
|
|
'context' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'target_scope' => [
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'entra_tenant_name' => (string) $tenant->name,
|
|
],
|
|
],
|
|
]);
|
|
|
|
TenantOnboardingSession::query()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'current_step' => 'verify',
|
|
'state' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'verification_operation_run_id' => (int) $run->getKey(),
|
|
],
|
|
'started_by_user_id' => (int) $user->getKey(),
|
|
'updated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(),
|
|
]);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
visit('/admin/onboarding')
|
|
->assertNoJavaScriptErrors()
|
|
->assertSee('Verify access')
|
|
->assertSee('Refresh')
|
|
->assertSee(OperationRunLinks::openLabel())
|
|
->assertDontSee('Start verification');
|
|
});
|
|
|
|
it('smokes onboarding completed verification details with secondary links revealed only in technical details', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$tenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => '17217217-2172-4172-9172-172172172172',
|
|
'external_id' => 'browser-spec172-complete',
|
|
'status' => Tenant::STATUS_ONBOARDING,
|
|
]);
|
|
$user = User::factory()->create(['name' => 'Spec172 Completed Browser Owner']);
|
|
|
|
createUserWithTenant(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
role: 'owner',
|
|
workspaceRole: 'owner',
|
|
ensureDefaultMicrosoftProviderConnection: false,
|
|
);
|
|
|
|
$connection = ProviderConnection::factory()->platform()->consentGranted()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'provider' => 'microsoft',
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'display_name' => 'Spec172 completed connection',
|
|
'is_default' => true,
|
|
'status' => 'connected',
|
|
]);
|
|
|
|
$previousRun = OperationRun::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'type' => 'provider.connection.check',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
'context' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'target_scope' => [
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
],
|
|
'verification_report' => VerificationReportWriter::build('provider.connection.check', []),
|
|
],
|
|
]);
|
|
|
|
$report = VerificationReportWriter::build('provider.connection.check', [
|
|
[
|
|
'key' => 'permissions.admin_consent',
|
|
'title' => 'Required application permissions',
|
|
'status' => 'fail',
|
|
'severity' => 'critical',
|
|
'blocking' => true,
|
|
'reason_code' => 'permission_denied',
|
|
'message' => 'Missing required Graph permissions.',
|
|
'evidence' => [],
|
|
'next_steps' => [],
|
|
],
|
|
]);
|
|
$report['previous_report_id'] = (int) $previousRun->getKey();
|
|
|
|
$currentRun = OperationRun::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'type' => 'provider.connection.check',
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'context' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'target_scope' => [
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'entra_tenant_name' => (string) $tenant->name,
|
|
],
|
|
'verification_report' => $report,
|
|
],
|
|
]);
|
|
|
|
TenantOnboardingSession::query()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'current_step' => 'verify',
|
|
'state' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'verification_operation_run_id' => (int) $currentRun->getKey(),
|
|
],
|
|
'started_by_user_id' => (int) $user->getKey(),
|
|
'updated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(),
|
|
]);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
$page = visit('/admin/onboarding');
|
|
|
|
$page
|
|
->assertNoJavaScriptErrors()
|
|
->assertSee('Verify access')
|
|
->assertSee('Technical details')
|
|
->assertSee(OperationRunLinks::openLabel())
|
|
->assertDontSee('Open previous operation')
|
|
->assertDontSee(OperationRunLinks::advancedMonitoringLabel())
|
|
->click('Technical details')
|
|
->waitForText('Verification technical details')
|
|
->assertNoJavaScriptErrors()
|
|
->assertSee('Open previous operation')
|
|
->assertSee(OperationRunLinks::advancedMonitoringLabel());
|
|
});
|