package cv import ( "context" "database/sql" "os" "path/filepath" "strings" "testing" _ "modernc.org/sqlite" ) func setupProfileDB(t *testing.T, name, email, location string) *sql.DB { t.Helper() db, err := sql.Open("sqlite", ":memory:") if err != nil { t.Fatal(err) } if _, err := db.Exec(`CREATE TABLE profile ( id INTEGER PRIMARY KEY CHECK (id = 1), name TEXT, email TEXT, location TEXT, timezone TEXT, salary_target_min REAL, salary_target_max REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )`); err != nil { t.Fatal(err) } if name != "" { if _, err := db.Exec(`INSERT INTO profile (id, name, email, location) VALUES (1, ?, ?, ?)`, name, email, location); err != nil { t.Fatal(err) } } return db } func TestLaTeXExporter_Export_EndToEnd(t *testing.T) { db := setupProfileDB(t, "Ada Lovelace", "ada@example.com", "London, UK") defer db.Close() tmp := t.TempDir() e := &LaTeXExporter{DB: db, OutputDir: tmp} out, err := e.Export(context.Background()) if err != nil { t.Fatalf("Export: %v", err) } if out.TeXPath == "" { t.Fatal("TeXPath empty") } if filepath.Dir(out.TeXPath) != tmp { t.Errorf("tex written outside OutputDir: %q", out.TeXPath) } data, err := os.ReadFile(out.TeXPath) if err != nil { t.Fatal(err) } s := string(data) if !strings.Contains(s, "Ada Lovelace") { t.Error("name not spliced") } if !strings.Contains(s, "ada@example.com") { t.Error("email not spliced") } if strings.Contains(s, "{{NAME}}") || strings.Contains(s, "{{EXPERIENCE}}") { t.Error("placeholders left unfilled in output") } if !strings.Contains(s, `\begin{document}`) || !strings.Contains(s, `\end{document}`) { t.Error("document structure missing") } // pdflatex may or may not be present in CI; either way the tex must succeed. // If present, PDFPath is populated; if absent, Issues notes the skip. if out.PDFPath == "" && len(out.Issues) == 0 { t.Log("pdflatex present but PDFPath unset — check compile path") } } func TestLaTeXExporter_LaTeXEscapingOnContact(t *testing.T) { db := setupProfileDB(t, "R&D Person", "x_y%z@example.com", "C#/OT") defer db.Close() e := &LaTeXExporter{DB: db, OutputDir: t.TempDir()} out, err := e.Export(context.Background()) if err != nil { t.Fatalf("Export: %v", err) } data, _ := os.ReadFile(out.TeXPath) s := string(data) // & should have been escaped to \&, % to \%, # to \#, _ to \_ for _, bad := range []string{"R&D Person", "C#/OT"} { if strings.Contains(s, bad) { t.Errorf("unescaped LaTeX special in output for %q", bad) } } if !strings.Contains(s, `R\&D Person`) { t.Error("missing escaped ampersand in name") } if !strings.Contains(s, `C\#/OT`) { t.Error("missing escaped hash in location") } } func TestValidate_CatchesStructuralGaps(t *testing.T) { badTex := `\documentclass{article} \begin{document} Hello. \end{document} ` issues := validate(badTex) if len(issues) == 0 { t.Fatal("expected structural issues") } var sawEducation, sawSubheading bool for _, i := range issues { if strings.Contains(i, "Education") { sawEducation = true } if strings.Contains(i, "resumeSubheading") { sawSubheading = true } } if !sawEducation || !sawSubheading { t.Errorf("missing expected issues: got %v", issues) } } func TestValidate_FlagsUnresolvedPlaceholders(t *testing.T) { // Minimal file with required markers but an unresolved placeholder. content := `\begin{document} \section{Education} \section{Work Experience} \section{Personal Projects} \section{Technical Skills} \resumeSubheading\resumeItem\resumeProjectHeading Name: {{NAME}} \end{document} ` issues := validate(content) joined := strings.Join(issues, " | ") if !strings.Contains(joined, "unresolved placeholders") { t.Errorf("expected unresolved-placeholder flag, got: %s", joined) } } func TestSanitizeFilename(t *testing.T) { cases := map[string]string{ "Ada Lovelace": "ada-lovelace", " Dr. María ": "dr-mara", // diacritics dropped (ASCII-only) "../../evil": "evil", "": "", } for in, want := range cases { if got := sanitizeFilename(in); got != want { t.Errorf("sanitizeFilename(%q) = %q, want %q", in, got, want) } } }