package worker import ( "os" "testing" "pdf-form-api/ai" "pdf-form-api/db" "pdf-form-api/models" ) func TestDefaultConfig(t *testing.T) { cfg := DefaultConfig() // When no env vars are set, APIKey should be empty if cfg.Model == "" { t.Error("expected default model") } if cfg.VisionModel == "" { t.Error("expected default vision model") } } func TestDefaultConfigWithEnvVars(t *testing.T) { os.Setenv("AI_BASE_URL", "https://custom.api.com") os.Setenv("AI_API_KEY", "test-key") os.Setenv("AI_MODEL", "custom-model") defer os.Unsetenv("AI_BASE_URL") defer os.Unsetenv("AI_API_KEY") defer os.Unsetenv("AI_MODEL") cfg := DefaultConfig() if cfg.BaseURL != "https://custom.api.com" { t.Errorf("expected custom base URL, got %s", cfg.BaseURL) } if cfg.APIKey != "test-key" { t.Errorf("expected test-key, got %s", cfg.APIKey) } if cfg.Model != "custom-model" { t.Errorf("expected custom-model, got %s", cfg.Model) } } func TestDefaultConfigDeepseekKey(t *testing.T) { os.Setenv("DEEPSEEK_API_KEY", "deepseek-key") defer os.Unsetenv("DEEPSEEK_API_KEY") os.Unsetenv("AI_API_KEY") cfg := DefaultConfig() if cfg.APIKey != "deepseek-key" { t.Errorf("expected deepseek-key, got %s", cfg.APIKey) } } func TestDefaultConfigDeepseekBaseURL(t *testing.T) { os.Unsetenv("AI_BASE_URL") os.Unsetenv("AI_API_KEY") os.Unsetenv("AI_MODEL") cfg := DefaultConfig() if cfg.BaseURL != "https://api.deepseek.com" { t.Errorf("expected default deepseek URL, got %s", cfg.BaseURL) } } func TestDefaultConfigVision(t *testing.T) { os.Unsetenv("AI_BASE_URL") os.Unsetenv("AI_API_KEY") os.Unsetenv("AI_MODEL") os.Unsetenv("OPENAI_API_KEY") os.Unsetenv("VISION_API_KEY") os.Unsetenv("VISION_BASE_URL") os.Unsetenv("VISION_MODEL") defer os.Setenv("VISION_MODEL", "") cfg := DefaultConfig() if cfg.VisionBaseURL != "https://api.openai.com/v1" { t.Errorf("expected OpenAI v1 URL, got %s", cfg.VisionBaseURL) } if cfg.VisionModel != "gpt-4o-mini" { t.Errorf("expected gpt-4o-mini, got %s", cfg.VisionModel) } } func TestDefaultConfigOpenAIKey(t *testing.T) { os.Setenv("OPENAI_API_KEY", "openai-key") defer os.Unsetenv("OPENAI_API_KEY") os.Unsetenv("VISION_API_KEY") os.Unsetenv("AI_API_KEY") cfg := DefaultConfig() if cfg.VisionAPIKey != "openai-key" { t.Errorf("expected openai-key, got %s", cfg.VisionAPIKey) } } func TestDefaultConfigVisionEnvVars(t *testing.T) { os.Setenv("VISION_BASE_URL", "https://custom.vision.api/v1") os.Setenv("VISION_API_KEY", "vision-key") os.Setenv("VISION_MODEL", "custom-vision") defer os.Unsetenv("VISION_BASE_URL") defer os.Unsetenv("VISION_API_KEY") defer os.Unsetenv("VISION_MODEL") cfg := DefaultConfig() if cfg.VisionBaseURL != "https://custom.vision.api/v1" { t.Errorf("expected custom vision URL, got %s", cfg.VisionBaseURL) } if cfg.VisionAPIKey != "vision-key" { t.Errorf("expected vision-key, got %s", cfg.VisionAPIKey) } if cfg.VisionModel != "custom-vision" { t.Errorf("expected custom-vision, got %s", cfg.VisionModel) } } func TestProcessPDFQuestionsNoAPIKey(t *testing.T) { dir := t.TempDir() dbPath := dir + "/test.db" conn, err := db.InitDB(dbPath, dir) if err != nil { t.Fatalf("InitDB: %v", err) } defer conn.Close() cfg := Config{} // No API key // Should not error, just skip ProcessPDFQuestions(conn, 1, "/fake.pdf", []models.FormField{ {Name: "Test", Type: models.FieldText}, }, cfg, dir) } func TestProcessPDFQuestionsNoFields(t *testing.T) { dir := t.TempDir() dbPath := dir + "/test.db" conn, err := db.InitDB(dbPath, dir) if err != nil { t.Fatalf("InitDB: %v", err) } defer conn.Close() cfg := Config{VisionAPIKey: "fake"} // Should not error, just skip ProcessPDFQuestions(conn, 1, "/fake.pdf", []models.FormField{}, cfg, dir) } func TestBuildFieldIDMap(t *testing.T) { fields := []models.FormField{ {ID: 1, Name: "Field1"}, {ID: 2, Name: "Field2"}, {ID: 3, Name: "Field3"}, } m := buildFieldIDMap(fields) if len(m) != 3 { t.Errorf("expected 3 entries, got %d", len(m)) } if m["Field1"] != 1 { t.Errorf("expected Field1 -> 1, got %d", m["Field1"]) } } func TestBuildLabelIDMap(t *testing.T) { fields := []models.FormField{ {ID: 5, Name: "FieldA"}, {ID: 12, Name: "FieldB"}, {ID: 23, Name: "FieldC"}, } m := buildLabelIDMap(fields) if len(m) != 3 { t.Errorf("expected 3 entries, got %d", len(m)) } if m["F5"] != 5 { t.Errorf("expected F5 -> 5, got %d", m["F5"]) } if m["F12"] != 12 { t.Errorf("expected F12 -> 12, got %d", m["F12"]) } if m["F23"] != 23 { t.Errorf("expected F23 -> 23, got %d", m["F23"]) } } func TestEnvOrDefault(t *testing.T) { os.Setenv("TEST_VAR", "value") defer os.Unsetenv("TEST_VAR") if result := envOrDefault("TEST_VAR", "default"); result != "value" { t.Errorf("expected 'value', got %q", result) } if result := envOrDefault("NONEXISTENT", "default"); result != "default" { t.Errorf("expected 'default', got %q", result) } } func TestProcessPDFQuestionsSkipsNoAPIKey(t *testing.T) { dir := t.TempDir() dbPath := dir + "/test.db" conn, err := db.InitDB(dbPath, dir) if err != nil { t.Fatalf("InitDB: %v", err) } defer conn.Close() cfg := Config{} // Should complete without error (skips due to no API key) ProcessPDFQuestions(conn, 1, "/nonexistent.pdf", []models.FormField{ {Name: "Test", Type: models.FieldText}, }, cfg, dir) } func TestProcessPDFQuestionsSkipsNoFields(t *testing.T) { dir := t.TempDir() dbPath := dir + "/test.db" conn, err := db.InitDB(dbPath, dir) if err != nil { t.Fatalf("InitDB: %v", err) } defer conn.Close() cfg := Config{APIKey: "text-key", VisionAPIKey: "vision-key"} // Should complete without error (skips due to no fields) ProcessPDFQuestions(conn, 1, "/nonexistent.pdf", []models.FormField{}, cfg, dir) } func TestValidateCoverageFull(t *testing.T) { vr := &ai.VisionResult{ LabelMap: map[string]int64{"a3f1": 1, "b7e2": 2, "c4d5": 3}, Fields: []ai.VisionFieldEntry{ {Label: "a3f1", Question: "What is name?", ValueGroup: "", WizardPage: 1}, {Label: "b7e2", Question: "What is email?", ValueGroup: "", WizardPage: 1}, {Label: "c4d5", Question: "What is phone?", ValueGroup: "", WizardPage: 2}, }, } fields := []models.FormField{ {ID: 1, Name: "Name"}, {ID: 2, Name: "Email"}, {ID: 3, Name: "Phone"}, } uncovered := validateCoverage(vr, fields) if len(uncovered) != 0 { t.Errorf("expected 0 uncovered, got %d: %v", len(uncovered), uncovered) } } func TestValidateCoverageMissingField(t *testing.T) { vr := &ai.VisionResult{ LabelMap: map[string]int64{"a3f1": 1, "b7e2": 2, "c4d5": 3}, Fields: []ai.VisionFieldEntry{ {Label: "a3f1", Question: "What is name?", ValueGroup: "", WizardPage: 1}, {Label: "b7e2", Question: "What is email?", ValueGroup: "", WizardPage: 1}, // c4d5 missing }, } fields := []models.FormField{ {ID: 1, Name: "Name"}, {ID: 2, Name: "Email"}, {ID: 3, Name: "Phone"}, } uncovered := validateCoverage(vr, fields) if len(uncovered) != 1 || uncovered[0] != 3 { t.Errorf("expected [3], got %v", uncovered) } } func TestValidateCoverageEmptyQuestion(t *testing.T) { vr := &ai.VisionResult{ LabelMap: map[string]int64{"a3f1": 1}, Fields: []ai.VisionFieldEntry{ {Label: "a3f1", Question: "", ValueGroup: "", WizardPage: 1}, }, } fields := []models.FormField{ {ID: 1, Name: "Name"}, } uncovered := validateCoverage(vr, fields) if len(uncovered) != 1 || uncovered[0] != 1 { t.Errorf("expected [1] (empty question counts as uncovered), got %v", uncovered) } } func TestValidateCoverageZeroWizardPage(t *testing.T) { vr := &ai.VisionResult{ LabelMap: map[string]int64{"a3f1": 1}, Fields: []ai.VisionFieldEntry{ {Label: "a3f1", Question: "What is name?", ValueGroup: "", WizardPage: 0}, }, } fields := []models.FormField{ {ID: 1, Name: "Name"}, } uncovered := validateCoverage(vr, fields) if len(uncovered) != 1 || uncovered[0] != 1 { t.Errorf("expected [1] (zero wizard_page counts as uncovered), got %v", uncovered) } } func TestFieldsIDsNotIn(t *testing.T) { fields := []models.FormField{ {ID: 1, Name: "A"}, {ID: 2, Name: "B"}, {ID: 3, Name: "C"}, } ids := map[int64]bool{2: true} out := fieldsIDsNotIn(fields, ids) if len(out) != 2 { t.Fatalf("expected 2, got %d", len(out)) } if out[0].ID != 1 || out[1].ID != 3 { t.Errorf("expected IDs 1 and 3, got %v and %v", out[0].ID, out[1].ID) } } func TestApplyDefaults(t *testing.T) { dir := t.TempDir() dbPath := dir + "/test.db" conn, err := db.InitDB(dbPath, dir) if err != nil { t.Fatalf("InitDB: %v", err) } defer conn.Close() // Insert a PDF and fields pdfID, err := db.InsertPDF(conn, "test.pdf", "test.pdf", "/fake/path", 100) if err != nil { t.Fatalf("InsertPDF: %v", err) } fields := []models.FormField{ {ID: 0, Name: "FieldA", Type: models.FieldText}, {ID: 0, Name: "FieldB", Type: models.FieldText}, } for i, f := range fields { fID, err := db.InsertFormField(conn, pdfID, f.Name, string(f.Type), "", "", "", "", false, 1, "") if err != nil { t.Fatalf("InsertFormField: %v", err) } fields[i] = f fields[i].ID = fID } applyDefaults(conn, pdfID, fields) // Verify questions were set for _, f := range fields { rows, err := db.GetFormFields(conn, pdfID) if err != nil { t.Fatalf("GetFormFields: %v", err) } for _, row := range rows { if int64(row[0].(int)) == f.ID { question := "" if row[10] != nil { question = row[10].(string) } if question == "" { t.Errorf("expected non-empty question for %s, got empty", f.Name) } } } } }