157 lines
4.2 KiB
Go
157 lines
4.2 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|