186 lines
7.7 KiB
PHP
186 lines
7.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Operations\TenantlessOperationRunViewer;
|
|
use App\Filament\Pages\TenantDashboard;
|
|
use App\Models\AuditLog;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\StoredReport;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Support\Audit\AuditActionId;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\Providers\ProviderReasonCodes;
|
|
use App\Support\Providers\ProviderVerificationStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Filament\Facades\Filament;
|
|
use Illuminate\Support\Facades\Bus;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Livewire\Livewire;
|
|
|
|
function supportDiagnosticsTenantAuditComponent(User $user, Tenant $tenant): \Livewire\Features\SupportTesting\Testable
|
|
{
|
|
test()->actingAs($user);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
setTenantPanelContext($tenant);
|
|
|
|
return Livewire::actingAs($user)->test(TenantDashboard::class);
|
|
}
|
|
|
|
function supportDiagnosticsOperationAuditComponent(User $user, OperationRun $run): \Livewire\Features\SupportTesting\Testable
|
|
{
|
|
test()->actingAs($user);
|
|
Filament::setTenant(null, true);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $run->workspace_id);
|
|
|
|
return Livewire::actingAs($user)->test(TenantlessOperationRunViewer::class, ['run' => $run]);
|
|
}
|
|
|
|
function seedSupportDiagnosticsAuditFixture(string $role = 'owner'): array
|
|
{
|
|
$tenant = Tenant::factory()->create(['name' => 'Audit Tenant']);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: $role);
|
|
|
|
$connection = ProviderConnection::factory()
|
|
->withCredential()
|
|
->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'display_name' => 'Audit connection',
|
|
'verification_status' => ProviderVerificationStatus::Blocked->value,
|
|
'last_error_reason_code' => ProviderReasonCodes::ProviderPermissionMissing,
|
|
'last_error_message' => 'raw-provider-secret-message',
|
|
'last_health_check_at' => now()->subMinutes(15),
|
|
]);
|
|
|
|
$run = OperationRun::factory()
|
|
->forTenant($tenant)
|
|
->create([
|
|
'type' => OperationRunType::BaselineCompare->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'summary_counts' => [
|
|
'total' => 0,
|
|
'processed' => 0,
|
|
],
|
|
'context' => [
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'raw_response_body' => 'secret-provider-body',
|
|
],
|
|
'completed_at' => now()->subMinutes(10),
|
|
]);
|
|
|
|
StoredReport::factory()->create([
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE,
|
|
'payload' => [
|
|
'raw_response_body' => 'stored-report-secret-body',
|
|
],
|
|
]);
|
|
|
|
AuditLog::query()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'action' => 'operation.failed',
|
|
'resource_type' => 'operation_run',
|
|
'resource_id' => (string) $run->getKey(),
|
|
'target_label' => 'Operation #'.$run->getKey(),
|
|
'metadata' => [
|
|
'raw_response_body' => 'audit-secret-body',
|
|
],
|
|
'outcome' => 'success',
|
|
'recorded_at' => now()->subMinutes(5),
|
|
]);
|
|
|
|
return [$user, $tenant, $run];
|
|
}
|
|
|
|
it('audits tenant support diagnostics opens with redacted metadata and no side effects', function (): void {
|
|
[$user, $tenant] = seedSupportDiagnosticsAuditFixture();
|
|
|
|
bindFailHardGraphClient();
|
|
Bus::fake();
|
|
Queue::fake();
|
|
|
|
$operationRunCount = OperationRun::query()->count();
|
|
|
|
assertNoOutboundHttp(function () use ($user, $tenant): void {
|
|
supportDiagnosticsTenantAuditComponent($user, $tenant)
|
|
->mountAction('openSupportDiagnostics')
|
|
->assertMountedActionModalSee('Support diagnostics');
|
|
});
|
|
|
|
Bus::assertNothingDispatched();
|
|
Queue::assertNothingPushed();
|
|
|
|
$audit = AuditLog::query()
|
|
->where('action', AuditActionId::SupportDiagnosticsOpened->value)
|
|
->latest('id')
|
|
->firstOrFail();
|
|
|
|
$metadataJson = json_encode($audit->metadata, JSON_THROW_ON_ERROR);
|
|
|
|
expect(OperationRun::query()->count())->toBe($operationRunCount)
|
|
->and(AuditLog::query()->where('action', AuditActionId::SupportDiagnosticsOpened->value)->count())->toBe(1)
|
|
->and(AuditLog::query()->where('action', AuditActionId::BaselineCompareStarted->value)->count())->toBe(0)
|
|
->and($audit->resource_type)->toBe('support_diagnostic_bundle')
|
|
->and($audit->resource_id)->toBe('tenant:'.$tenant->getKey())
|
|
->and($audit->operation_run_id)->toBeNull()
|
|
->and($audit->metadata['context_type'] ?? null)->toBe('tenant')
|
|
->and($audit->metadata['redaction_mode'] ?? null)->toBe('default_redacted')
|
|
->and($audit->metadata['section_count'] ?? null)->toBe(8)
|
|
->and($audit->metadata['reference_count'] ?? null)->toBeGreaterThan(0)
|
|
->and($audit->metadata['primary_context_id'] ?? null)->toBe((string) $tenant->getKey())
|
|
->and($metadataJson)->not->toContain('raw-provider-secret-message')
|
|
->and($metadataJson)->not->toContain('secret-provider-body')
|
|
->and($metadataJson)->not->toContain('stored-report-secret-body')
|
|
->and($metadataJson)->not->toContain('audit-secret-body');
|
|
});
|
|
|
|
it('audits operation support diagnostics opens with redacted metadata and no side effects', function (): void {
|
|
[$user, $tenant, $run] = seedSupportDiagnosticsAuditFixture();
|
|
|
|
bindFailHardGraphClient();
|
|
Bus::fake();
|
|
Queue::fake();
|
|
|
|
$operationRunCount = OperationRun::query()->count();
|
|
|
|
assertNoOutboundHttp(function () use ($user, $run): void {
|
|
supportDiagnosticsOperationAuditComponent($user, $run)
|
|
->mountAction('openSupportDiagnostics')
|
|
->assertMountedActionModalSee('Support diagnostics');
|
|
});
|
|
|
|
Bus::assertNothingDispatched();
|
|
Queue::assertNothingPushed();
|
|
|
|
$audit = AuditLog::query()
|
|
->where('action', AuditActionId::SupportDiagnosticsOpened->value)
|
|
->latest('id')
|
|
->firstOrFail();
|
|
|
|
$metadataJson = json_encode($audit->metadata, JSON_THROW_ON_ERROR);
|
|
|
|
expect(OperationRun::query()->count())->toBe($operationRunCount)
|
|
->and(AuditLog::query()->where('action', AuditActionId::SupportDiagnosticsOpened->value)->count())->toBe(1)
|
|
->and(AuditLog::query()->where('action', AuditActionId::BaselineCompareStarted->value)->count())->toBe(0)
|
|
->and($audit->resource_type)->toBe('support_diagnostic_bundle')
|
|
->and($audit->resource_id)->toBe('operation_run:'.$run->getKey())
|
|
->and($audit->operation_run_id)->toBe((int) $run->getKey())
|
|
->and($audit->metadata['context_type'] ?? null)->toBe('operation_run')
|
|
->and($audit->metadata['redaction_mode'] ?? null)->toBe('default_redacted')
|
|
->and($audit->metadata['section_count'] ?? null)->toBe(8)
|
|
->and($audit->metadata['reference_count'] ?? null)->toBeGreaterThan(0)
|
|
->and($audit->metadata['primary_context_id'] ?? null)->toBe((string) $run->getKey())
|
|
->and($metadataJson)->not->toContain('raw-provider-secret-message')
|
|
->and($metadataJson)->not->toContain('secret-provider-body')
|
|
->and($metadataJson)->not->toContain('stored-report-secret-body')
|
|
->and($metadataJson)->not->toContain('audit-secret-body');
|
|
}); |