Added `BaselineSubjectResolution` page and supporting logic to visualize missing identities, ambiguous matches, and skipped coverages as defined in Spec 384. Replaces legacy compare warnings with an actionable, deterministic UI surface. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #455
165 lines
6.1 KiB
PHP
165 lines
6.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\User;
|
|
use App\Support\Baselines\SubjectClass;
|
|
use App\Support\ManagedEnvironmentLinks;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\Resources\ResourceIdentity;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\Feature\Baselines\Support\BaselineSubjectResolutionFixtures;
|
|
|
|
pest()->browser()->timeout(45_000);
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('Spec384 smokes baseline subject resolution reachability and action modal', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
$tenant->forceFill(['name' => 'Spec384 Subject Resolution Environment'])->save();
|
|
|
|
$run = spec384BrowserSubjectResolutionRun($tenant);
|
|
$resolutionPath = spec384BrowserPath(ManagedEnvironmentLinks::baselineSubjectResolutionUrl($tenant, [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
]));
|
|
|
|
$page = visit(spec384BrowserLoginUrl($user, $tenant, $resolutionPath))
|
|
->resize(1440, 1100)
|
|
->waitForText('Baseline subject resolution')
|
|
->assertSee('Spec384 Subject Resolution Environment')
|
|
->assertSee('1 decision required')
|
|
->assertSee('Decision worklist')
|
|
->assertSee('Duplicate policy')
|
|
->assertSee('Unresolved Duplicate Candidates')
|
|
->assertSee('Binding required')
|
|
->assertSee('TenantPilot-only')
|
|
->assertSee('Open operation')
|
|
->assertSee('Open baseline compare')
|
|
->assertSee('Bind subject')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs()
|
|
->screenshot(true, spec384BrowserScreenshotName('01-baseline-subject-resolution'));
|
|
|
|
spec384BrowserCopyScreenshot('01-baseline-subject-resolution');
|
|
|
|
$page
|
|
->click('Bind subject')
|
|
->waitForText('Record a TenantPilot-only subject binding')
|
|
->assertSee('Provider resource')
|
|
->assertSee('Operator note')
|
|
->assertSee('This does not mutate the provider tenant')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs()
|
|
->screenshot(true, spec384BrowserScreenshotName('02-baseline-subject-resolution-bind-modal'));
|
|
|
|
spec384BrowserCopyScreenshot('02-baseline-subject-resolution-bind-modal');
|
|
|
|
$page
|
|
->resize(430, 900)
|
|
->assertScript('document.documentElement.scrollWidth <= window.innerWidth', true)
|
|
->assertNoJavaScriptErrors()
|
|
->screenshot(true, spec384BrowserScreenshotName('03-baseline-subject-resolution-mobile'));
|
|
|
|
spec384BrowserCopyScreenshot('03-baseline-subject-resolution-mobile');
|
|
});
|
|
|
|
function spec384BrowserSubjectResolutionRun(ManagedEnvironment $tenant)
|
|
{
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
$leftIdentity = ResourceIdentity::providerResource('fake-provider', 'policy', 'candidate-left');
|
|
$rightIdentity = ResourceIdentity::providerResource('fake-provider', 'policy', 'candidate-right');
|
|
$leftDescriptor = BaselineSubjectResolutionFixtures::providerDescriptor($leftIdentity, 'Duplicate policy');
|
|
$rightDescriptor = BaselineSubjectResolutionFixtures::providerDescriptor($rightIdentity, 'Duplicate policy');
|
|
|
|
BaselineSubjectResolutionFixtures::inventoryCandidate(
|
|
$tenant,
|
|
$leftIdentity,
|
|
'Duplicate policy',
|
|
);
|
|
BaselineSubjectResolutionFixtures::inventoryCandidate(
|
|
$tenant,
|
|
$rightIdentity,
|
|
'Duplicate policy',
|
|
);
|
|
|
|
return seedBaselineCompareRun(
|
|
tenant: $tenant,
|
|
profile: $profile,
|
|
snapshot: $snapshot,
|
|
compareContext: [
|
|
'result_semantics' => [
|
|
'version' => 1,
|
|
'subject_outcomes' => [
|
|
BaselineSubjectResolutionFixtures::semanticOutcome([
|
|
'reason' => 'unresolved_duplicate_candidates',
|
|
'actionability' => 'binding_required',
|
|
'readiness_impact' => 'customer_blocker',
|
|
'subject' => [
|
|
'subject_domain' => 'baseline',
|
|
'subject_class' => SubjectClass::PolicyBacked->value,
|
|
'subject_type_key' => 'deviceConfiguration',
|
|
'subject_key' => 'legacy-display-key',
|
|
'display_label' => 'Duplicate policy',
|
|
'candidate_descriptors' => [
|
|
$leftDescriptor->toArray(),
|
|
$rightDescriptor->toArray(),
|
|
],
|
|
],
|
|
]),
|
|
],
|
|
],
|
|
],
|
|
status: OperationRunStatus::Completed->value,
|
|
outcome: OperationRunOutcome::PartiallySucceeded->value,
|
|
);
|
|
}
|
|
|
|
function spec384BrowserLoginUrl(User $user, ManagedEnvironment $tenant, string $redirect): string
|
|
{
|
|
return route('admin.local.smoke-login', [
|
|
'email' => $user->email,
|
|
'tenant' => $tenant->external_id,
|
|
'workspace' => $tenant->workspace->slug,
|
|
'redirect' => $redirect,
|
|
]);
|
|
}
|
|
|
|
function spec384BrowserPath(string $url): string
|
|
{
|
|
$path = parse_url($url, PHP_URL_PATH) ?: '/admin';
|
|
$query = parse_url($url, PHP_URL_QUERY);
|
|
|
|
return is_string($query) && $query !== '' ? $path.'?'.$query : $path;
|
|
}
|
|
|
|
function spec384BrowserScreenshotName(string $name): string
|
|
{
|
|
return 'spec384-'.$name;
|
|
}
|
|
|
|
function spec384BrowserCopyScreenshot(string $name): void
|
|
{
|
|
$filename = spec384BrowserScreenshotName($name).'.png';
|
|
$source = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$targetDirectory = repo_path('specs/384-baseline-subject-resolution-ui/artifacts/screenshots');
|
|
|
|
if (! is_dir($targetDirectory)) {
|
|
@mkdir($targetDirectory, 0755, true);
|
|
}
|
|
|
|
if (! is_file($source)) {
|
|
$source = \Pest\Browser\Support\Screenshot::path($filename);
|
|
}
|
|
|
|
for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) {
|
|
usleep(100_000);
|
|
clearstatcache(true, $source);
|
|
}
|
|
|
|
if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
|
|
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$filename);
|
|
}
|
|
}
|