Files
crewli/api/tests/Unit/FormBuilder/Purposes/PurposeGuardProvidersTest.php
bert.hausmans e3c9211e3f feat(form-builder): wire PurposeGuardProvider per purpose (WS-6)
Adds PurposeGuardProvider as a parallel interface to PurposeDefinition
(value object stays untouched). Seven concrete providers, one per v1.0
purpose, each declaring its publish-guard list. Registry resolves and
caches providers via guards_class config key.

Universal guards (MaxOneIdentityKeyPerTargetEntity,
AppendStrategyRequiresCollectionTarget, NoAmbiguousTrustLevels,
IdentityKeyBindingsOnlyInFirstSection) wire into every purpose. The
section guard is a cheap no-op when section_level_submit=false.

ArtistAdvanceGuards omits RequiresIdentityKeyBinding because the
artist subject is resolved via portal token, not form data. Same
reasoning for supplier_intake (production_request) and the auth-based
purposes.

Includes a cross-cutting BindingTypeRegistryConsistencyTest that
verifies tasks 5/7/8 do not contradict each other (registry ↔ guards ↔
purpose required_bindings).

Refs: RFC-WS-6.md §3 (Q9, Q13)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 23:01:19 +02:00

91 lines
3.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\FormBuilder\Purposes;
use App\FormBuilder\Publishing\PublishGuard;
use App\FormBuilder\Purposes\Guards\ArtistAdvanceGuards;
use App\FormBuilder\Purposes\Guards\EventRegistrationGuards;
use App\FormBuilder\Purposes\Guards\IncidentReportGuards;
use App\FormBuilder\Purposes\Guards\PostEventEvaluationGuards;
use App\FormBuilder\Purposes\Guards\SignatureContractGuards;
use App\FormBuilder\Purposes\Guards\SupplierIntakeGuards;
use App\FormBuilder\Purposes\Guards\UserProfileGuards;
use Tests\TestCase;
final class PurposeGuardProvidersTest extends TestCase
{
public function test_event_registration_guards_include_purpose_specific_codes(): void
{
$codes = $this->codesFor(EventRegistrationGuards::class);
$this->assertContains('requires_identity_key_binding:person:email', $codes);
$this->assertContains('max_one_identity_key_per_target_entity', $codes);
$this->assertContains('requires_field_type:EMAIL', $codes);
$this->assertContains('conditional:availability_picker_requires_event', $codes);
$this->assertContains('conditional:tag_picker_requires_tag_categories', $codes);
$this->assertContains('append_strategy_requires_collection_target', $codes);
$this->assertContains('no_ambiguous_trust_levels', $codes);
$this->assertContains('identity_key_bindings_only_in_first_section', $codes);
}
public function test_artist_advance_guards_omit_identity_key_requirement(): void
{
$codes = $this->codesFor(ArtistAdvanceGuards::class);
$this->assertNotContains('requires_identity_key_binding:person:email', $codes);
$this->assertContains('max_one_identity_key_per_target_entity', $codes);
$this->assertContains('identity_key_bindings_only_in_first_section', $codes);
}
public function test_supplier_intake_guards_universal_only(): void
{
$codes = $this->codesFor(SupplierIntakeGuards::class);
$this->assertContains('max_one_identity_key_per_target_entity', $codes);
$this->assertContains('append_strategy_requires_collection_target', $codes);
$this->assertContains('no_ambiguous_trust_levels', $codes);
$this->assertContains('identity_key_bindings_only_in_first_section', $codes);
}
public function test_post_event_evaluation_guards_universal_only(): void
{
$codes = $this->codesFor(PostEventEvaluationGuards::class);
$this->assertContains('max_one_identity_key_per_target_entity', $codes);
$this->assertContains('append_strategy_requires_collection_target', $codes);
$this->assertContains('no_ambiguous_trust_levels', $codes);
$this->assertContains('identity_key_bindings_only_in_first_section', $codes);
}
public function test_incident_report_guards_universal_only(): void
{
$codes = $this->codesFor(IncidentReportGuards::class);
$this->assertContains('max_one_identity_key_per_target_entity', $codes);
$this->assertContains('append_strategy_requires_collection_target', $codes);
}
public function test_signature_contract_guards_universal_only(): void
{
$codes = $this->codesFor(SignatureContractGuards::class);
$this->assertContains('max_one_identity_key_per_target_entity', $codes);
}
public function test_user_profile_guards_universal_only(): void
{
$codes = $this->codesFor(UserProfileGuards::class);
$this->assertContains('max_one_identity_key_per_target_entity', $codes);
}
/**
* @param class-string $providerClass
* @return list<string>
*/
private function codesFor(string $providerClass): array
{
$provider = $this->app->make($providerClass);
return array_map(
static fn (PublishGuard $g): string => $g->code(),
$provider->publishGuards(),
);
}
}