32 new tests covering D1 deliverables:
- Migration shape (3): failure_response_code column presence,
type/length/nullability, index name. MySQL information_schema
introspection.
- Exception hierarchy (11): abstract base, RuntimeException ancestor,
per-subclass constructor + reasonCode (named-args asserting
submissionId is preserved structurally), Timeout extends Infra and
inherits temporary_error, all subclasses extend base, previous-throwable
chaining works, IdentityMatchInvariantViolation is NOT in the
binding-applicator hierarchy and IS a DomainException.
- FormBindingExceptionClassifier matrix (6): each subclass maps to its
reason code; Timeout dispatches to inherited 'temporary_error';
arbitrary RuntimeException -> 'unknown_error'; IdentityMatchInvariantViolation
-> 'unknown_error' (intentional fallback per docstring).
- FormFieldBindingMergeStrategy::validForTargetType (4 tests covering
the full 4 strategies x 3 target types matrix).
- FormSubmissionIdentityMatchResolved (4): ShouldBroadcast contract,
private channel naming ('private-submission.{id}'), broadcast-as
string, payload assignment.
- FormSubmission failure_response_code cast (4): persists as plain
string, NULL by default, factory state composes with apply_status,
round-trips for all four canonical codes.
Baseline regenerated to absorb new tautological-assertion entries (48
lines) — these are class-hierarchy regression guards that Larastan
correctly flags as statically known. The pattern is established in the
codebase per existing baseline entries for similar tests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
77 lines
2.9 KiB
PHP
77 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\FormBuilder\Bindings;
|
|
|
|
use App\Exceptions\FormBuilder\FormBindingApplicatorTimeoutException;
|
|
use App\Exceptions\FormBuilder\FormBindingDataIntegrityException;
|
|
use App\Exceptions\FormBuilder\FormBindingInfraException;
|
|
use App\Exceptions\FormBuilder\FormBindingSchemaConfigException;
|
|
use App\Exceptions\FormBuilder\IdentityMatchInvariantViolation;
|
|
use App\FormBuilder\Bindings\FormBindingExceptionClassifier;
|
|
use PHPUnit\Framework\TestCase;
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* Per RFC-WS-6 §Q3 v1.3 addition 2.
|
|
*
|
|
* The classifier is a single-responsibility helper: Throwable in,
|
|
* failure_response_code string out. Centralised so the listener catch
|
|
* block (D2) and the retry-service recordFailure (D2) produce identical
|
|
* classifications.
|
|
*/
|
|
final class FormBindingExceptionClassifierTest extends TestCase
|
|
{
|
|
public function test_classifies_schema_config_exception(): void
|
|
{
|
|
$code = FormBindingExceptionClassifier::classify(
|
|
new FormBindingSchemaConfigException(submissionId: '01HX', message: 'x'),
|
|
);
|
|
$this->assertSame('schema_config_error', $code);
|
|
}
|
|
|
|
public function test_classifies_infra_exception(): void
|
|
{
|
|
$code = FormBindingExceptionClassifier::classify(
|
|
new FormBindingInfraException(submissionId: '01HX', message: 'x'),
|
|
);
|
|
$this->assertSame('temporary_error', $code);
|
|
}
|
|
|
|
public function test_classifies_timeout_as_temporary_error(): void
|
|
{
|
|
// Subclass dispatch — Timeout extends Infra, inherits reasonCode.
|
|
$code = FormBindingExceptionClassifier::classify(
|
|
new FormBindingApplicatorTimeoutException(submissionId: '01HX', message: 'deadline'),
|
|
);
|
|
$this->assertSame('temporary_error', $code);
|
|
}
|
|
|
|
public function test_classifies_data_integrity_exception(): void
|
|
{
|
|
$code = FormBindingExceptionClassifier::classify(
|
|
new FormBindingDataIntegrityException(submissionId: '01HX', message: 'fk'),
|
|
);
|
|
$this->assertSame('data_integrity_error', $code);
|
|
}
|
|
|
|
public function test_classifies_arbitrary_runtime_exception_as_unknown(): void
|
|
{
|
|
$code = FormBindingExceptionClassifier::classify(new RuntimeException('boom'));
|
|
$this->assertSame('unknown_error', $code);
|
|
}
|
|
|
|
public function test_classifies_identity_match_invariant_violation_as_unknown(): void
|
|
{
|
|
// IdentityMatchInvariantViolation is intentionally NOT in the
|
|
// FormBindingApplicatorException hierarchy (per RFC-WS-6 §Q2 — the
|
|
// listener that throws it runs outside the binding-applicator
|
|
// pipeline). It falls through to 'unknown_error' here, which is
|
|
// the right response-shape because users cannot meaningfully act
|
|
// on it; admins triage via GlitchTip.
|
|
$code = FormBindingExceptionClassifier::classify(new IdentityMatchInvariantViolation('x'));
|
|
$this->assertSame('unknown_error', $code);
|
|
}
|
|
}
|