package validator import ( "errors" "testing" ) // Helpers — mirror the Rust test helpers. func mkLookup(records ...WorkerRecord) WorkerLookup { return NewInMemoryWorkerLookup(records) } func mkWorker(id, name, status, city, state, role string) WorkerRecord { return WorkerRecord{ CandidateID: id, Name: name, Status: status, City: strPtr(city), State: strPtr(state), Role: strPtr(role), } } func asValidationError(err error) (*ValidationError, bool) { var ve *ValidationError if errors.As(err, &ve) { return ve, true } return nil, false } // ── Schema-level errors ── func TestFill_WrongArtifactType_FailsSchema(t *testing.T) { v := NewFillValidator(mkLookup()) _, err := v.Validate(Artifact{EmailDraft: map[string]any{}}) ve, ok := asValidationError(err) if !ok { t.Fatalf("expected ValidationError, got %v", err) } if ve.Kind != ErrSchema || ve.Field != "artifact" { t.Errorf("expected schema/artifact error, got %+v", ve) } } func TestFill_MissingFillsArray_FailsSchema(t *testing.T) { v := NewFillValidator(mkLookup()) _, err := v.Validate(Artifact{FillProposal: map[string]any{}}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrSchema || ve.Field != "fills" { t.Errorf("expected schema/fills error, got %+v", ve) } } func TestFill_MissingCandidateID_FailsSchema(t *testing.T) { v := NewFillValidator(mkLookup()) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "fills": []any{ map[string]any{"name": "Alice"}, }, }}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrSchema || ve.Field != "fills[0].candidate_id" { t.Errorf("expected schema/fills[0].candidate_id error, got %+v", ve) } } // ── Completeness ── func TestFill_TargetCountMismatch_FailsCompleteness(t *testing.T) { v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "active", "Toledo", "OH", "Welder"))) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "_context": map[string]any{"target_count": float64(2)}, "fills": []any{map[string]any{"candidate_id": "w1", "name": "Alice"}}, }}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrCompleteness { t.Errorf("expected completeness error, got %+v", ve) } } // ── Cross-roster checks ── func TestFill_PhantomID_FailsConsistency(t *testing.T) { // Lookup is empty → any candidate_id is "phantom" — the // load-bearing check for the 0→85% pattern. v := NewFillValidator(mkLookup()) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "fills": []any{map[string]any{"candidate_id": "phantom-id", "name": "Alice"}}, }}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrConsistency { t.Errorf("expected consistency error on phantom ID, got %+v", ve) } } func TestFill_DuplicateID_FailsConsistency(t *testing.T) { v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "active", "Toledo", "OH", "Welder"))) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "fills": []any{ map[string]any{"candidate_id": "w1", "name": "Alice"}, map[string]any{"candidate_id": "w1", "name": "Alice"}, }, }}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrConsistency { t.Errorf("expected consistency error on duplicate ID, got %+v", ve) } } func TestFill_InactiveStatus_FailsConsistency(t *testing.T) { v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "inactive", "Toledo", "OH", "Welder"))) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "fills": []any{map[string]any{"candidate_id": "w1", "name": "Alice"}}, }}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrConsistency { t.Errorf("expected consistency error on inactive status, got %+v", ve) } } func TestFill_Blacklist_FailsPolicy(t *testing.T) { w := mkWorker("w1", "Alice", "active", "Toledo", "OH", "Welder") w.BlacklistedClients = []string{"CLI-99"} v := NewFillValidator(mkLookup(w)) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "_context": map[string]any{"client_id": "cli-99"}, // case-insensitive "fills": []any{map[string]any{"candidate_id": "w1", "name": "Alice"}}, }}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrPolicy { t.Errorf("expected policy error on blacklist, got %+v", ve) } } func TestFill_GeoMismatch_FailsConsistency(t *testing.T) { // Worker in Detroit, contract says Toledo. v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "active", "Detroit", "MI", "Welder"))) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "_context": map[string]any{"city": "Toledo", "state": "OH"}, "fills": []any{map[string]any{"candidate_id": "w1", "name": "Alice"}}, }}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrConsistency { t.Errorf("expected consistency error on geo mismatch, got %+v", ve) } } func TestFill_RoleMismatch_FailsConsistency(t *testing.T) { v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "active", "Toledo", "OH", "Forklift Operator"))) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "_context": map[string]any{"role": "Welder"}, "fills": []any{map[string]any{"candidate_id": "w1", "name": "Alice"}}, }}) ve, _ := asValidationError(err) if ve == nil || ve.Kind != ErrConsistency { t.Errorf("expected consistency error on role mismatch, got %+v", ve) } } // ── Happy path ── func TestFill_WellFormed_Passes(t *testing.T) { v := NewFillValidator(mkLookup( mkWorker("w1", "Alice", "active", "Toledo", "OH", "Welder"), mkWorker("w2", "Bob", "active", "Toledo", "OH", "Welder"), )) report, err := v.Validate(Artifact{FillProposal: map[string]any{ "_context": map[string]any{ "target_count": float64(2), "city": "Toledo", "state": "OH", "role": "Welder", }, "fills": []any{ map[string]any{"candidate_id": "w1", "name": "Alice"}, map[string]any{"candidate_id": "w2", "name": "Bob"}, }, }}) if err != nil { t.Fatalf("expected pass, got %v", err) } if len(report.Findings) != 0 { t.Errorf("expected zero findings, got %v", report.Findings) } } // ── Name mismatch is a Finding (warning), not an error ── func TestFill_NameMismatch_EmitsWarning(t *testing.T) { v := NewFillValidator(mkLookup(mkWorker("w1", "Alice Smith", "active", "Toledo", "OH", "Welder"))) report, err := v.Validate(Artifact{FillProposal: map[string]any{ "fills": []any{ map[string]any{"candidate_id": "w1", "name": "Alyssa Smith"}, // typo / outdated }, }}) if err != nil { t.Fatalf("name mismatch should NOT error, got %v", err) } if len(report.Findings) != 1 || report.Findings[0].Severity != SeverityWarning { t.Errorf("expected 1 warning finding, got %v", report.Findings) } } // ── Case-insensitive matches ── func TestFill_CaseInsensitiveMatch_Passes(t *testing.T) { v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "ACTIVE", "TOLEDO", "oh", "Welder"))) _, err := v.Validate(Artifact{FillProposal: map[string]any{ "_context": map[string]any{"city": "Toledo", "state": "OH"}, "fills": []any{map[string]any{"candidate_id": "w1", "name": "Alice"}}, }}) if err != nil { t.Errorf("case-insensitive comparisons should pass, got %v", err) } } // ── Validator name is stable ── func TestFill_NameMatchesRust(t *testing.T) { v := NewFillValidator(mkLookup()) if v.Name() != "staffing.fill" { t.Errorf("name should match Rust 'staffing.fill', got %q", v.Name()) } }