127 lines
5.8 KiB
PHP
127 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Support\Livewire\TrustedState\TrustedStateClass;
|
|
use App\Support\Livewire\TrustedState\TrustedStatePolicy;
|
|
use App\Support\Livewire\TrustedState\TrustedStateResolver;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
|
|
function livewireTrustedStateFirstSliceFixtures(): array
|
|
{
|
|
return [
|
|
TrustedStatePolicy::MANAGED_TENANT_ONBOARDING_WIZARD => 'app/Filament/Pages/Workspaces/ManagedTenantOnboardingWizard.php',
|
|
TrustedStatePolicy::TENANT_REQUIRED_PERMISSIONS => 'app/Filament/Pages/TenantRequiredPermissions.php',
|
|
TrustedStatePolicy::SYSTEM_RUNBOOKS => 'app/Filament/System/Pages/Ops/Runbooks.php',
|
|
];
|
|
}
|
|
|
|
function livewireTrustedStateFieldGroups(array $componentPolicy): array
|
|
{
|
|
return [
|
|
'locked_identity_fields' => $componentPolicy['locked_identity_fields'],
|
|
'mutable_selector_fields' => $componentPolicy['mutable_selector_fields'],
|
|
'server_derived_authority_fields' => $componentPolicy['server_derived_authority_fields'],
|
|
];
|
|
}
|
|
|
|
it('documents a first-slice trusted-state policy for every guarded surface', function (): void {
|
|
$policy = app(TrustedStatePolicy::class);
|
|
|
|
expect($policy->components())
|
|
->toBe(array_keys(livewireTrustedStateFirstSliceFixtures()));
|
|
|
|
foreach (livewireTrustedStateFirstSliceFixtures() as $component => $relativePath) {
|
|
expect(file_exists(base_path($relativePath)))->toBeTrue();
|
|
expect($policy->forComponent($component)['component_name'])->not->toBe('');
|
|
}
|
|
});
|
|
|
|
it('keeps first-slice policies explicit about state lanes and authority sources', function (): void {
|
|
$policy = app(TrustedStatePolicy::class);
|
|
$validClasses = array_map(
|
|
static fn (TrustedStateClass $class): string => $class->value,
|
|
TrustedStateClass::cases(),
|
|
);
|
|
|
|
expect($validClasses)->toContain('presentation', 'locked_identity', 'server_derived_authority');
|
|
|
|
foreach ($policy->firstSlice() as $componentPolicy) {
|
|
expect($componentPolicy['plane'])->not->toBe('')
|
|
->and($componentPolicy['authority_sources'])->not->toBeEmpty()
|
|
->and($componentPolicy['mutable_selectors'])->toBeArray()
|
|
->and($componentPolicy['locked_identities'])->toBeArray()
|
|
->and($componentPolicy['locked_identity_fields'])->toBeArray()
|
|
->and($componentPolicy['mutable_selector_fields'])->toBeArray()
|
|
->and($componentPolicy['server_derived_authority_fields'])->toBeArray()
|
|
->and($componentPolicy['forbidden_public_authority_fields'])->toBeArray();
|
|
}
|
|
});
|
|
|
|
it('keeps the first-slice trusted-state inventory implementation-backed', function (): void {
|
|
$policy = app(TrustedStatePolicy::class);
|
|
|
|
foreach (livewireTrustedStateFirstSliceFixtures() as $component => $relativePath) {
|
|
$componentPolicy = $policy->forComponent($component);
|
|
$contents = file_get_contents(base_path($relativePath));
|
|
|
|
expect($contents)->not->toBeFalse();
|
|
|
|
foreach (livewireTrustedStateFieldGroups($componentPolicy) as $groupName => $fields) {
|
|
foreach ($fields as $field) {
|
|
expect($field['notes'])->not->toBe('');
|
|
expect($field['implementation_markers'])->not->toBeEmpty();
|
|
|
|
$expectedStateClass = match ($groupName) {
|
|
'locked_identity_fields' => TrustedStateClass::LockedIdentity->value,
|
|
'mutable_selector_fields' => TrustedStateClass::Presentation->value,
|
|
'server_derived_authority_fields' => TrustedStateClass::ServerDerivedAuthority->value,
|
|
default => throw new InvalidArgumentException('Unknown trusted-state group.'),
|
|
};
|
|
|
|
expect($field['state_class'])->toBe($expectedStateClass);
|
|
|
|
foreach ($field['implementation_markers'] as $marker) {
|
|
if (str_contains($marker, PHP_EOL)) {
|
|
$pattern = '/'.str_replace(
|
|
['\\ '.preg_quote(PHP_EOL, '/').'\\ ', '\\ '.preg_quote(PHP_EOL, '/'), preg_quote(PHP_EOL, '/').'\\ '],
|
|
['\\s+', '\\s+', '\\s+'],
|
|
preg_quote($marker, '/')
|
|
).'/s';
|
|
|
|
expect(preg_match($pattern, (string) $contents))
|
|
->toBe(1, "Missing implementation marker [{$marker}] for {$component}.{$field['name']}");
|
|
|
|
continue;
|
|
}
|
|
|
|
expect(str_contains((string) $contents, $marker))
|
|
->toBeTrue("Missing implementation marker [{$marker}] for {$component}.{$field['name']}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
it('documents first-slice trusted-state helper semantics through reusable enum and resolver APIs', function (): void {
|
|
$policy = app(TrustedStatePolicy::class);
|
|
$resolver = app(TrustedStateResolver::class);
|
|
|
|
expect(TrustedStateClass::Presentation->allowsClientMutation())->toBeTrue()
|
|
->and(TrustedStateClass::Presentation->requiresServerRevalidation())->toBeFalse()
|
|
->and(TrustedStateClass::LockedIdentity->allowsClientMutation())->toBeFalse()
|
|
->and(TrustedStateClass::LockedIdentity->requiresServerRevalidation())->toBeTrue()
|
|
->and(TrustedStateClass::ServerDerivedAuthority->allowsClientMutation())->toBeFalse()
|
|
->and(TrustedStateClass::ServerDerivedAuthority->requiresServerRevalidation())->toBeTrue();
|
|
|
|
foreach ($policy->components() as $component) {
|
|
expect($resolver->requiredAuthoritySources($component, $policy))
|
|
->toBe($policy->forComponent($component)['authority_sources']);
|
|
}
|
|
});
|
|
|
|
it('binds the shared trusted-state resolver through the container', function (): void {
|
|
expect(app(TrustedStateResolver::class))->toBeInstanceOf(TrustedStateResolver::class)
|
|
->and(app(WorkspaceContext::class))->toBeInstanceOf(WorkspaceContext::class);
|
|
});
|