create(['name' => 'Old Co']); $submission = $this->makeSubmission($company, [ 'name' => ['value' => 'New Co BV', 'trust' => 70], ]); $result = DB::transaction(fn (): BindingPassResult => resolve(FormBindingApplicator::class)->apply($submission)); $this->assertSame(ApplyStatus::COMPLETED, $result->applyStatus()); $this->assertSame('company', $result->provisionedSubjectType); $company->refresh(); $this->assertSame('New Co BV', $company->name); } public function test_conflict_resolution_picks_highest_trust(): void { $company = Company::factory()->create(['name' => 'Initial']); $submission = $this->makeSubmission($company, [ 'name__low' => ['value' => 'LowTrust Co', 'trust' => 30, 'attribute' => 'name'], 'name__high' => ['value' => 'HighTrust Co', 'trust' => 90, 'attribute' => 'name'], ]); $result = DB::transaction(fn (): BindingPassResult => resolve(FormBindingApplicator::class)->apply($submission)); $this->assertSame(ApplyStatus::COMPLETED, $result->applyStatus()); $company->refresh(); $this->assertSame('HighTrust Co', $company->name); } public function test_missing_company_subject_throws_resolution_exception(): void { $org = Organisation::factory()->create(); $schema = FormSchema::factory()->create([ 'organisation_id' => $org->id, 'purpose' => FormPurpose::SUPPLIER_INTAKE->value, ]); // production_request flow would have set subject_type='company' + // subject_id; without it, the resolver throws. $submission = FormSubmission::factory()->create([ 'form_schema_id' => $schema->id, 'subject_type' => null, 'subject_id' => null, ]); $submission->schema_snapshot = ['fields' => []]; $submission->save(); try { DB::transaction(fn () => resolve(FormBindingApplicator::class)->apply($submission->fresh())); $this->fail('Expected PurposeSubjectResolutionException'); } catch (PurposeSubjectResolutionException $e) { $this->assertSame('supplier_intake', $e->purposeSlug); $this->assertSame('no_production_request', $e->reasonCode); } } /** * @param array $bindingSpecs */ private function makeSubmission(Company $company, array $bindingSpecs): FormSubmission { $schema = FormSchema::factory()->create([ 'organisation_id' => $company->organisation_id, 'purpose' => FormPurpose::SUPPLIER_INTAKE->value, ]); $submission = FormSubmission::factory()->create([ 'form_schema_id' => $schema->id, 'subject_type' => 'company', 'subject_id' => $company->id, ]); $snapshotFields = []; foreach ($bindingSpecs as $slug => $spec) { $attribute = $spec['attribute'] ?? $slug; $field = FormField::factory()->create([ 'form_schema_id' => $schema->id, 'slug' => $slug, ]); $binding = FormFieldBinding::factory()->forField($field) ->entityOwned('company', $attribute) ->create([ 'merge_strategy' => FormFieldBindingMergeStrategy::Overwrite->value, 'trust_level' => $spec['trust'], ]); $snapshotFields[] = [ 'id' => (string) $field->id, 'slug' => (string) $field->slug, 'sort_order' => (int) $field->sort_order, 'bindings' => [[ 'id' => (string) $binding->id, 'mode' => 'entity_owned', 'entity' => 'company', 'column' => $attribute, 'merge_strategy' => 'overwrite', 'trust_level' => $spec['trust'], 'is_identity_key' => false, ]], ]; $this->writeValue($submission->id, $field->id, $spec['value']); } $submission->schema_snapshot = ['fields' => $snapshotFields]; $submission->save(); return $submission->fresh(); } private function writeValue(string $submissionId, string $fieldId, mixed $value): void { $row = new FormValue; $row->form_submission_id = $submissionId; $row->form_field_id = $fieldId; $row->setAttribute('value', $value); $row->value_anonymised = false; $row->save(); } }