package annotator import ( "strings" "testing" "pdf-form-api/models" "pdf-form-api/renderer" ) func TestBuildFieldContexts(t *testing.T) { fields := []models.FormField{ {Name: "First Name", Type: models.FieldText, Page: 1, Rect: "100 700 300 720"}, {Name: "Last Name", Type: models.FieldText, Page: 1, Rect: "100 650 300 670"}, {Name: "Email", Type: models.FieldText, Page: 2, Rect: "100 500 300 520"}, } pages := []renderer.PageInfo{ {Number: 1, Width: 612, Height: 792}, {Number: 2, Width: 612, Height: 792}, } grouped := BuildFieldContexts(fields, pages) if len(grouped[1]) != 2 { t.Errorf("expected 2 fields on page 1, got %d", len(grouped[1])) } if len(grouped[2]) != 1 { t.Errorf("expected 1 field on page 2, got %d", len(grouped[2])) } // First field should be "First Name" (higher Y = top of page) if grouped[1][0].FieldName != "First Name" { t.Errorf("expected first field on page 1 to be 'First Name', got %s", grouped[1][0].FieldName) } // Position descriptions if grouped[1][0].Position != "at the top of the page" { t.Errorf("expected top position, got %s", grouped[1][0].Position) } if grouped[1][1].Position != "at the bottom of the page" { t.Errorf("expected bottom position, got %s", grouped[1][1].Position) } } func TestBuildFieldContextsSinglePage(t *testing.T) { fields := []models.FormField{ {Name: "Only Field", Type: models.FieldText, Page: 1, Rect: "100 700 300 720"}, } pages := []renderer.PageInfo{ {Number: 1, Width: 612, Height: 792}, } grouped := BuildFieldContexts(fields, pages) if grouped[1][0].Position != "the only field on this page" { t.Errorf("expected single field position, got %s", grouped[1][0].Position) } } func TestPagePrompt(t *testing.T) { ctxs := []FieldContext{ { FieldName: "Given Name", FieldType: "text", Position: "at the top", Surrounding: []string{"Family Name", "Email"}, }, { FieldName: "Country", FieldType: "combobox", Choices: []string{"USA", "Canada", "Mexico"}, Position: "in the middle", }, } prompt := PagePrompt(1, ctxs) if len(prompt) == 0 { t.Error("prompt should not be empty") } if !contains(prompt, "Given Name") { t.Error("prompt should contain field name 'Given Name'") } if !contains(prompt, "Country") { t.Error("prompt should contain field name 'Country'") } if !contains(prompt, "text") { t.Error("prompt should contain field type 'text'") } if !contains(prompt, "combobox") { t.Error("prompt should contain field type 'combobox'") } if !contains(prompt, "USA") { t.Error("prompt should contain choices") } } func TestPositionDescription(t *testing.T) { tests := []struct { index int total int expect string }{ {0, 1, "the only field on this page"}, {0, 3, "at the top of the page"}, {2, 3, "at the bottom of the page"}, {1, 4, "in the upper half of the page"}, {2, 4, "in the lower half of the page"}, {3, 4, "at the bottom of the page"}, } for _, tc := range tests { result := positionDescription(tc.index, tc.total) if result != tc.expect { t.Errorf("positionDescription(%d, %d) = %q, want %q", tc.index, tc.total, result, tc.expect) } } } func TestParseY(t *testing.T) { tests := []struct { rect string expect float64 }{ {"100 700 300 720", 700}, {"0 0 100 200", 0}, {"invalid", 0}, {"", 0}, } for _, tc := range tests { result := parseY(tc.rect) if result != tc.expect { t.Errorf("parseY(%q) = %f, want %f", tc.rect, result, tc.expect) } } } func contains(s, substr string) bool { return len(s) > 0 && len(substr) > 0 && (len(s) >= len(substr)) && (s == substr || containsHelper(s, substr)) } func containsHelper(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false } func TestBuildVisionPrompt(t *testing.T) { fields := []VisionFieldMeta{ {ID: 1, Name: "GivenName", FieldType: "text", Label: "a3f1", Page: 1}, {ID: 2, Name: "Country", FieldType: "combobox", Choices: []string{"USA", "Canada"}, Label: "b7e2", Page: 1}, {ID: 3, Name: "Date", FieldType: "text", DefaultValue: "MM/DD/YYYY", Label: "c4d5", Page: 2}, } prompt := BuildVisionPrompt(fields, 2) if len(prompt) == 0 { t.Error("prompt should not be empty") } if !strings.Contains(prompt, "a3f1") { t.Error("prompt should contain label a3f1") } if !strings.Contains(prompt, "b7e2") { t.Error("prompt should contain label b7e2") } if !strings.Contains(prompt, "GivenName") { t.Error("prompt should contain field name GivenName") } if !strings.Contains(prompt, "combobox") { t.Error("prompt should contain field type combobox") } if !strings.Contains(prompt, "USA") { t.Error("prompt should contain choices") } if !strings.Contains(prompt, "MM/DD/YYYY") { t.Error("prompt should contain default value") } if !strings.Contains(prompt, "description") { t.Error("prompt should request description") } if !strings.Contains(prompt, "fields") { t.Error("prompt should request fields array") } if !strings.Contains(prompt, "question") { t.Error("prompt should request question per field") } if !strings.Contains(prompt, "value_group") { t.Error("prompt should request value_group per field") } if !strings.Contains(prompt, "wizard_page") { t.Error("prompt should request wizard_page per field") } // Verify wizard_page prompt clarifies it is NOT PDF page number if !strings.Contains(prompt, "NOT the PDF page number") { t.Error("prompt should clarify wizard_page is not PDF page number") } // Verify non-sequential label instruction if !strings.Contains(prompt, "NON-SEQUENTIAL") { t.Error("prompt should mention labels are non-sequential") } } func TestBuildVisionPromptEmptyFields(t *testing.T) { prompt := BuildVisionPrompt([]VisionFieldMeta{}, 1) if len(prompt) == 0 { t.Error("prompt should not be empty even with no fields") } } func TestBuildVisionPromptIncludesValueGroups(t *testing.T) { fields := []VisionFieldMeta{ {ID: 1, Name: "GivenName", FieldType: "text", Label: "a3f1", Page: 1}, {ID: 2, Name: "GivenName2", FieldType: "text", Label: "b7e2", Page: 2}, } prompt := BuildVisionPrompt(fields, 2) if !strings.Contains(prompt, "value_group") { t.Error("prompt should request value_group per field") } if !strings.Contains(prompt, "same value") { t.Error("prompt should explain same-value grouping") } } func TestBuildVisionPromptIncludesWizardPages(t *testing.T) { fields := []VisionFieldMeta{ {ID: 1, Name: "FirstName", FieldType: "text", Label: "a3f1", Page: 1}, {ID: 2, Name: "LastName", FieldType: "text", Label: "b7e2", Page: 1}, } prompt := BuildVisionPrompt(fields, 1) if !strings.Contains(prompt, "wizard_page") { t.Error("prompt should request wizard_page per field") } if !strings.Contains(prompt, "label") { t.Error("prompt should reference label field") } if !strings.Contains(prompt, "question") { t.Error("prompt should reference question field") } }