makeSubmission([ $this->fieldRow('email', 'person', 'email', trustLevel: 80, value: 'a@example.nl'), ]); $resolved = $this->resolver()->resolve($submission); $this->assertCount(1, $resolved); $this->assertSame('a@example.nl', $resolved[0]->value); $this->assertSame('person', $resolved[0]->targetEntity); $this->assertSame(BindingTargetType::SCALAR, $resolved[0]->targetType); } public function test_higher_trust_wins(): void { $submission = $this->makeSubmission([ $this->fieldRow('email_low', 'person', 'email', trustLevel: 30, value: 'low@example.nl'), $this->fieldRow('email_high', 'person', 'email', trustLevel: 90, value: 'high@example.nl'), ]); $resolved = $this->resolver()->resolve($submission); $this->assertCount(1, $resolved); $this->assertSame('high@example.nl', $resolved[0]->value); } public function test_equal_trust_breaks_by_sort_order(): void { $submission = $this->makeSubmission([ $this->fieldRow('first_email', 'person', 'email', trustLevel: 50, value: 'first@example.nl', sortOrder: 1), $this->fieldRow('second_email', 'person', 'email', trustLevel: 50, value: 'second@example.nl', sortOrder: 0), ]); $resolved = $this->resolver()->resolve($submission); $this->assertCount(1, $resolved); // sort_order 0 wins over sort_order 1 at equal trust $this->assertSame('second@example.nl', $resolved[0]->value); } public function test_field_absent_from_form_values_is_not_a_candidate(): void { $submission = $this->makeSubmission([ $this->fieldRow('email_skipped', 'person', 'email', trustLevel: 90, writeValue: false), $this->fieldRow('first_name', 'person', 'first_name', trustLevel: 50, value: 'Jan'), ]); $resolved = $this->resolver()->resolve($submission); $this->assertCount(1, $resolved); $this->assertSame('person.first_name', $resolved[0]->targetEntity . '.' . $resolved[0]->targetAttribute); } public function test_explicit_null_value_is_a_candidate(): void { $submission = $this->makeSubmission([ $this->fieldRow('email', 'person', 'email', trustLevel: 80, value: ''), ]); $resolved = $this->resolver()->resolve($submission); $this->assertCount(1, $resolved); $this->assertTrue($resolved[0]->valueIsExplicit); } public function test_section_filter_includes_only_matching_section(): void { $submission = $this->makeSubmission([ $this->fieldRow('a_email', 'person', 'email', trustLevel: 80, value: 'a@example.nl', sectionSlug: 'section-a'), $this->fieldRow('b_first_name', 'person', 'first_name', trustLevel: 80, value: 'Jan', sectionSlug: 'section-b'), ]); $submission->schema_snapshot = array_merge($submission->schema_snapshot, [ 'sections' => [ ['id' => 'sec-a-id', 'slug' => 'section-a'], ['id' => 'sec-b-id', 'slug' => 'section-b'], ], ]); $submission->save(); $resolved = $this->resolver()->resolve($submission, sectionId: 'sec-a-id'); $this->assertCount(1, $resolved); $this->assertSame('person.email', $resolved[0]->targetEntity . '.' . $resolved[0]->targetAttribute); } public function test_null_section_id_returns_all_candidates(): void { $submission = $this->makeSubmission([ $this->fieldRow('a_email', 'person', 'email', trustLevel: 80, value: 'a@example.nl', sectionSlug: 'section-a'), $this->fieldRow('b_first_name', 'person', 'first_name', trustLevel: 80, value: 'Jan', sectionSlug: 'section-b'), ]); $resolved = $this->resolver()->resolve($submission); $this->assertCount(2, $resolved); } public function test_different_target_groups_resolved_independently(): void { $submission = $this->makeSubmission([ $this->fieldRow('email', 'person', 'email', trustLevel: 80, value: 'a@example.nl'), $this->fieldRow('first_name', 'person', 'first_name', trustLevel: 70, value: 'Jan'), $this->fieldRow('last_name', 'person', 'last_name', trustLevel: 60, value: 'Jansen'), ]); $resolved = $this->resolver()->resolve($submission); $this->assertCount(3, $resolved); $attributes = array_map(fn (ResolvedBinding $r): string => $r->targetAttribute, $resolved); $this->assertContains('email', $attributes); $this->assertContains('first_name', $attributes); $this->assertContains('last_name', $attributes); } public function test_empty_form_values_returns_empty(): void { $submission = $this->makeSubmission([]); $this->assertSame([], $this->resolver()->resolve($submission)); } private function resolver(): BindingConflictResolver { return $this->app->make(BindingConflictResolver::class); } /** * @param list> $fieldRows */ private function makeSubmission(array $fieldRows): FormSubmission { $schema = FormSchema::factory()->create(); $submission = FormSubmission::factory()->create([ 'form_schema_id' => $schema->id, 'organisation_id' => $schema->organisation_id, ]); $snapshotFields = []; foreach ($fieldRows as $row) { $field = FormField::factory()->create([ 'form_schema_id' => $schema->id, 'slug' => $row['slug'], 'sort_order' => $row['sort_order'], ]); $snapshotFields[] = [ 'id' => (string) $field->id, 'slug' => (string) $row['slug'], 'sort_order' => (int) $row['sort_order'], 'section_slug' => $row['section_slug'] ?? null, 'bindings' => [[ 'id' => 'bnd-' . $row['slug'], 'mode' => 'entity_owned', 'entity' => $row['entity'], 'column' => $row['column'], 'merge_strategy' => FormFieldBindingMergeStrategy::Overwrite->value, 'trust_level' => $row['trust_level'], 'is_identity_key' => false, ]], ]; if ($row['write_value'] ?? true) { $val = new FormValue(); $val->form_submission_id = $submission->id; $val->form_field_id = $field->id; $val->setAttribute('value', $row['value']); $val->value_anonymised = false; $val->save(); } } $submission->schema_snapshot = ['fields' => $snapshotFields]; $submission->save(); return $submission->fresh(); } /** * @return array */ private function fieldRow( string $slug, string $entity, string $column, int $trustLevel, mixed $value = null, int $sortOrder = 0, bool $writeValue = true, ?string $sectionSlug = null, ): array { return [ 'slug' => $slug, 'entity' => $entity, 'column' => $column, 'trust_level' => $trustLevel, 'value' => $value ?? '', 'sort_order' => $sortOrder, 'write_value' => $writeValue, 'section_slug' => $sectionSlug, ]; } }