package pdf import ( "os" "path/filepath" "testing" ) const samplePDF = "../samples/OoPdfFormExample.pdf" func TestExtractFieldsSamplePDF(t *testing.T) { fields, err := ExtractFields(samplePDF) if err != nil { t.Fatalf("ExtractFields returned error: %v", err) } if len(fields) == 0 { t.Fatal("expected at least one form field") } t.Logf("Extracted %d fields", len(fields)) } func TestExtractFieldsReturnsExpectedCount(t *testing.T) { fields, err := ExtractFields(samplePDF) if err != nil { t.Fatalf("ExtractFields: %v", err) } expectedCount := 17 if len(fields) != expectedCount { t.Errorf("expected %d fields, got %d", expectedCount, len(fields)) } } func TestExtractFieldsReturnsExpectedNames(t *testing.T) { fields, err := ExtractFields(samplePDF) if err != nil { t.Fatalf("ExtractFields: %v", err) } expectedNames := []string{ "Given Name Text Box", "Family Name Text Box", "House nr Text Box", "Address 1 Text Box", "Address 2 Text Box", "City Text Box", "Postcode Text Box", "Country Combo Box", "Height Formatted Field", "Driving License Check Box", "Favourite Colour List Box", "Gender List Box", } nameMap := make(map[string]bool) for _, f := range fields { nameMap[f.Name] = true } for _, name := range expectedNames { if !nameMap[name] { t.Errorf("expected field %q to be present", name) } } } func TestExtractFieldsReturnsExpectedTypes(t *testing.T) { fields, err := ExtractFields(samplePDF) if err != nil { t.Fatalf("ExtractFields: %v", err) } typeMap := make(map[string]string) for _, f := range fields { typeMap[f.Name] = string(f.Type) } // Text fields textFields := []string{"Given Name Text Box", "Family Name Text Box", "City Text Box", "Postcode Text Box"} for _, name := range textFields { if typeMap[name] != "text" { t.Errorf("expected %s to be text, got %s", name, typeMap[name]) } } // Checkbox fields checkFields := []string{"Driving License Check Box", "Language 1 Check Box"} for _, name := range checkFields { if typeMap[name] != "checkbox" { t.Errorf("expected %s to be checkbox, got %s", name, typeMap[name]) } } // Combo/list fields comboFields := []string{"Country Combo Box", "Favourite Colour List Box", "Gender List Box"} for _, name := range comboFields { if typeMap[name] != "combobox" && typeMap[name] != "listbox" { t.Errorf("expected %s to be combo or list, got %s", name, typeMap[name]) } } } func TestExtractFieldsComboChoices(t *testing.T) { fields, err := ExtractFields(samplePDF) if err != nil { t.Fatalf("ExtractFields: %v", err) } for _, f := range fields { if f.Name == "Country Combo Box" { if len(f.Choices) == 0 { t.Error("Country Combo Box should have choices") } found := false for _, c := range f.Choices { if c == "Germany" { found = true break } } if !found { t.Error("Country Combo Box should include 'Germany' in choices") } } if f.Name == "Favourite Colour List Box" { if len(f.Choices) == 0 { t.Error("Favourite Colour List Box should have choices") } found := false for _, c := range f.Choices { if c == "Red" { found = true break } } if !found { t.Error("Favourite Colour List Box should include 'Red' in choices") } } } } func TestExtractFieldsDefaultValue(t *testing.T) { fields, err := ExtractFields(samplePDF) if err != nil { t.Fatalf("ExtractFields: %v", err) } for _, f := range fields { if f.Name == "Height Formatted Field" { if f.DefaultVal != "150" { t.Errorf("Height Formatted Field default should be '150', got %q", f.DefaultVal) } } if f.Name == "Gender List Box" { if f.DefaultVal != "Man" { t.Errorf("Gender List Box default should be 'Man', got %q", f.DefaultVal) } } } } func TestExtractFieldsCurrentValues(t *testing.T) { fields, err := ExtractFields(samplePDF) if err != nil { t.Fatalf("ExtractFields: %v", err) } for _, f := range fields { if f.Name == "Height Formatted Field" { if f.Value != "150" { t.Errorf("Height Formatted Field value should be '150', got %q", f.Value) } } if f.Name == "Gender List Box" { if f.Value != "Man" { t.Errorf("Gender List Box value should be 'Man', got %q", f.Value) } } if f.Name == "Favourite Colour List Box" { if f.Value != "Red" { t.Errorf("Favourite Colour List Box value should be 'Red', got %q", f.Value) } } } } func TestExtractFieldsNoDuplicates(t *testing.T) { fields, err := ExtractFields(samplePDF) if err != nil { t.Fatalf("ExtractFields: %v", err) } nameMap := make(map[string]int) for _, f := range fields { nameMap[f.Name]++ } for name, count := range nameMap { if count > 1 { t.Errorf("duplicate field name %q (count: %d)", name, count) } } } func TestExtractFieldsNonExistent(t *testing.T) { _, err := ExtractFields("/nonexistent/path.pdf") if err == nil { t.Error("expected error for non-existent file") } } func TestExtractFieldsNotAPDF(t *testing.T) { tmp := t.TempDir() path := filepath.Join(tmp, "not_a_pdf.txt") if err := os.WriteFile(path, []byte("not a pdf"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } _, err := ExtractFields(path) if err == nil { t.Error("expected error for non-PDF file") } } func TestExtractFieldsNoForm(t *testing.T) { // Copy the sample and remove form (the sample always has a form, so create a minimal valid PDF) tmp := t.TempDir() path := filepath.Join(tmp, "empty.pdf") // Minimal valid PDF with no form content := "%PDF-1.0\n1 0 obj<>endobj\n2 0 obj<>endobj\nxref\n0 3\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \ntrailer<>\nstartxref\n102\n%%EOF" if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } _, err := ExtractFields(path) if err == nil { t.Error("expected error for PDF with no form") } } func TestFillPDFTextFields(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{ "Given Name Text Box": "John", "Family Name Text Box": "Doe", "City Text Box": "Berlin", }) if err != nil { t.Fatalf("FillPDF returned error: %v", err) } if buf.Len() == 0 { t.Fatal("filled PDF should not be empty") } // Verify it's a valid PDF (starts with %PDF) if buf.Bytes()[0] != '%' || string(buf.Bytes()[:5])[:5] != "%PDF-" { t.Fatal("output should be a valid PDF starting with %%PDF-") } } func TestFillPDFCheckbox(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{ "Driving License Check Box": "true", }) if err != nil { t.Fatalf("FillPDF returned error: %v", err) } if buf.Len() == 0 { t.Fatal("filled PDF should not be empty") } if buf.Bytes()[0] != '%' || string(buf.Bytes()[:5])[:5] != "%PDF-" { t.Fatal("output should be a valid PDF starting with %%PDF-") } } func TestFillPDFComboBox(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{ "Country Combo Box": "Germany", }) if err != nil { t.Fatalf("FillPDF returned error: %v", err) } if buf.Len() == 0 { t.Fatal("filled PDF should not be empty") } if buf.Bytes()[0] != '%' || string(buf.Bytes()[:5])[:5] != "%PDF-" { t.Fatal("output should be a valid PDF starting with %%PDF-") } } func TestFillPDFListBox(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{ "Gender List Box": "Woman", }) if err != nil { t.Fatalf("FillPDF returned error: %v", err) } if buf.Len() == 0 { t.Fatal("filled PDF should not be empty") } if buf.Bytes()[0] != '%' || string(buf.Bytes()[:5])[:5] != "%PDF-" { t.Fatal("output should be a valid PDF starting with %%PDF-") } } func TestFillPDFAllFieldTypes(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{ "Given Name Text Box": "John", "Family Name Text Box": "Doe", "House nr Text Box": "42", "Address 1 Text Box": "Main Street", "Address 2 Text Box": "Apt 5B", "City Text Box": "Berlin", "Postcode Text Box": "10115", "Country Combo Box": "Germany", "Height Formatted Field": "180", "Driving License Check Box": "true", "Language 1 Check Box": "true", "Language 2 Check Box": "true", "Favourite Colour List Box": "Blue", "Gender List Box": "Woman", }) if err != nil { t.Fatalf("FillPDF returned error: %v", err) } if buf.Len() == 0 { t.Fatal("filled PDF should not be empty") } // Verify output is larger than zero and starts with PDF header if buf.Len() < 100 { t.Fatal("filled PDF seems too small") } if buf.Bytes()[0] != '%' || string(buf.Bytes()[:5])[:5] != "%PDF-" { t.Fatal("output should be a valid PDF starting with %%PDF-") } } func TestFillPDFWithUnknownField(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{ "NonExistentField": "value", "Given Name Text Box": "John", }) if err != nil { t.Fatalf("FillPDF should not fail for unknown fields: %v", err) } if buf.Len() == 0 { t.Fatal("filled PDF should not be empty") } } func TestFillPDFEmptyMap(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{}) if err != nil { t.Fatalf("FillPDF with empty map should succeed: %v", err) } if buf.Len() == 0 { t.Fatal("filled PDF should not be empty") } } func TestFillPDFNonExistent(t *testing.T) { _, err := FillPDF("/nonexistent/path.pdf", map[string]string{"test": "val"}) if err == nil { t.Error("expected error for non-existent file") } } func TestFillPDFCheckboxOffValue(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{ "Driving License Check Box": "false", "Language 1 Check Box": "off", }) if err != nil { t.Fatalf("FillPDF returned error: %v", err) } if buf.Len() == 0 { t.Fatal("filled PDF should not be empty") } } func TestFillPDFCheckboxOnValues(t *testing.T) { for _, val := range []string{"true", "True", "TRUE", "yes", "Yes", "on", "On", "/Yes"} { buf, err := FillPDF(samplePDF, map[string]string{ "Driving License Check Box": val, }) if err != nil { t.Fatalf("FillPDF with value %q returned error: %v", val, err) } if buf.Len() == 0 { t.Errorf("filled PDF should not be empty for value %q", val) } } } func TestFillPDFProducesValidPDF(t *testing.T) { buf, err := FillPDF(samplePDF, map[string]string{ "Given Name Text Box": "John", "Family Name Text Box": "Doe", }) if err != nil { t.Fatalf("FillPDF: %v", err) } // Save to temp file and verify we can read it back tmp := t.TempDir() path := filepath.Join(tmp, "filled.pdf") if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } // Read back the filled PDF and extract fields fields, err := ExtractFields(path) if err != nil { t.Fatalf("ExtractFields on filled PDF: %v", err) } if len(fields) == 0 { t.Error("filled PDF should still have form fields") } } func TestFillPDFValuesPreserved(t *testing.T) { fill := map[string]string{ "Given Name Text Box": "TestName", "Family Name Text Box": "TestFamily", "City Text Box": "TestCity", } buf, err := FillPDF(samplePDF, fill) if err != nil { t.Fatalf("FillPDF: %v", err) } // Save and re-read to verify values are present tmp := t.TempDir() path := filepath.Join(tmp, "filled.pdf") if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } fields, err := ExtractFields(path) if err != nil { t.Fatalf("ExtractFields on filled PDF: %v", err) } for _, f := range fields { if expected, ok := fill[f.Name]; ok { if f.Value == "" { t.Errorf("field %q: expected non-empty value, got empty", f.Name) } // Values may be plain text or UTF-16BE encoded with BOM in the PDF if f.Value == expected { continue } // Build expected UTF-16BE with BOM (each char is \x00 + char for ASCII) utf16WithBOM := "\xfe\xff" for _, r := range expected { utf16WithBOM += "\x00" + string(r) } if f.Value != utf16WithBOM { t.Errorf("field %q: expected %q or UTF-16BE %q, got %q", f.Name, expected, utf16WithBOM, f.Value) } } } } func TestFillPDFDoesNotModifyOriginal(t *testing.T) { originalSize, err := os.Stat(samplePDF) if err != nil { t.Fatalf("Stat: %v", err) } _, err = FillPDF(samplePDF, map[string]string{ "Given Name Text Box": "John", }) if err != nil { t.Fatalf("FillPDF: %v", err) } newStat, err := os.Stat(samplePDF) if err != nil { t.Fatalf("Stat after fill: %v", err) } if newStat.Size() != originalSize.Size() { t.Errorf("original PDF should not be modified: was %d, now %d", originalSize.Size(), newStat.Size()) } }