package handler import ( "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) func TestRecognizeVirtualConvert_MatrixAndPrecedence(t *testing.T) { root := t.TempDir() write := func(rel string) { p := filepath.Join(root, filepath.FromSlash(rel)) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte("x"), 0o644); err != nil { t.Fatal(err) } } // Sources on disk: doc.md, only.docx, both.md + both.docx, page.html. write("doc.md") write("only.docx") write("both.md") write("both.docx") write("page.html") cases := []struct { name string url string wantOK bool wantSrcExt string wantFormat string }{ {"md→docx", "/doc.docx", true, ".md", "docx"}, {"md→html", "/doc.html", true, ".md", "html"}, {"md→pdf", "/doc.pdf", true, ".md", "pdf"}, {"docx→md (only docx present)", "/only.md", true, ".docx", "md"}, {"docx→html (only docx present)", "/only.html", true, ".docx", "html"}, {"docx has no pdf source", "/only.pdf", false, "", ""}, {"both present, html prefers md source", "/both.html", true, ".md", "html"}, {"html→md", "/page.md", true, ".html", "md"}, {"html→docx", "/page.docx", true, ".html", "docx"}, {"no source at all", "/missing.html", false, "", ""}, {"directory url ignored", "/doc/", false, "", ""}, {"non-convertible target", "/doc.txt", false, "", ""}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { src, format, ok := RecognizeVirtualConvert(root, c.url) if ok != c.wantOK { t.Fatalf("ok=%v want %v (src=%q format=%q)", ok, c.wantOK, src, format) } if !ok { return } if format != c.wantFormat { t.Errorf("format=%q want %q", format, c.wantFormat) } if filepath.Ext(src) != c.wantSrcExt { t.Errorf("source ext=%q want %q (src=%q)", filepath.Ext(src), c.wantSrcExt, src) } }) } } func TestServeFrontMatterTemplate(t *testing.T) { rec := httptest.NewRecorder() ServeFrontMatterTemplate(rec, httptest.NewRequest(http.MethodGet, FrontMatterTemplatePath, nil)) if rec.Code != http.StatusOK { t.Fatalf("status=%d, want 200", rec.Code) } if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "application/json") { t.Errorf("Content-Type=%q, want application/json", ct) } var payload struct { Placeholder string `json:"placeholder"` Fields []struct { Name string `json:"name"` Hint string `json:"hint"` } `json:"fields"` } if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { t.Fatalf("decode: %v; body=%s", err, rec.Body.String()) } if len(payload.Fields) == 0 { t.Fatal("fields empty") } // The two keys with no other source are the ones authors most need hinted. for _, want := range []string{"doctype", "numbering"} { if !strings.Contains(payload.Placeholder, want) { t.Errorf("placeholder missing %q: %q", want, payload.Placeholder) } } // HEAD returns headers, no body. hrec := httptest.NewRecorder() ServeFrontMatterTemplate(hrec, httptest.NewRequest(http.MethodHead, FrontMatterTemplatePath, nil)) if hrec.Code != http.StatusOK || hrec.Body.Len() != 0 { t.Errorf("HEAD: status=%d bodylen=%d, want 200 + empty", hrec.Code, hrec.Body.Len()) } // Non-GET/HEAD is rejected. prec := httptest.NewRecorder() ServeFrontMatterTemplate(prec, httptest.NewRequest(http.MethodPost, FrontMatterTemplatePath, nil)) if prec.Code != http.StatusMethodNotAllowed { t.Errorf("POST: status=%d, want 405", prec.Code) } }