feat: tests for http handlers and render package

This commit is contained in:
2026-02-02 10:09:01 +01:00
parent 36ac3f03aa
commit 16ff836274
2 changed files with 327 additions and 0 deletions

View File

@ -1,6 +1,10 @@
package render
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
@ -50,3 +54,92 @@ Check this out [here](http://tatata.toto)
}
}
}
func TestTemplateManagerGetTemplate_Caches(t *testing.T) {
baseDir := t.TempDir()
writeTemplate(t, baseDir, "base.tmpl", `{{ define "base" }}base{{ end }}`)
writeTemplate(t, baseDir, "index.tmpl", `{{ define "content" }}index{{ end }}`)
tm := NewTemplateManager(baseDir)
td := &TemplateData{
Name: "index",
FileNameSet: []string{"base", "index"},
}
first, err := tm.GetTemplate(td)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
second, err := tm.GetTemplate(td)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if first != second {
t.Fatalf("expected cached template instance")
}
}
func TestTemplateManagerGetTemplate_Missing(t *testing.T) {
baseDir := t.TempDir()
tm := NewTemplateManager(baseDir)
td := &TemplateData{
Name: "missing",
FileNameSet: []string{"missing"},
}
if _, err := tm.GetTemplate(td); err == nil {
t.Fatalf("expected error for missing template")
}
}
func TestTemplateManagerRender(t *testing.T) {
baseDir := t.TempDir()
writeTemplate(t, baseDir, "base.tmpl", `{{ define "base" }}<html>{{ template "content" . }}</html>{{ end }}`)
writeTemplate(t, baseDir, "noteList.tmpl", `{{ define "noteList" }}notes{{ end }}`)
writeTemplate(t, baseDir, "index.tmpl", `{{ define "content" }}hello{{ end }}`)
tm := NewTemplateManager(baseDir)
rec := httptest.NewRecorder()
err := tm.Render(rec, "index", map[string]string{"msg": "hi"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ct := rec.Header().Get("Content-Type"); ct != "text/html; charset=utf-8" {
t.Fatalf("expected content-type text/html; charset=utf-8, got %q", ct)
}
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "hello") {
t.Fatalf("expected rendered template content")
}
}
func TestTemplateManagerRender_MissingTemplate(t *testing.T) {
baseDir := t.TempDir()
writeTemplate(t, baseDir, "base.tmpl", `{{ define "base" }}<html>{{ template "content" . }}</html>{{ end }}`)
writeTemplate(t, baseDir, "noteList.tmpl", `{{ define "noteList" }}notes{{ end }}`)
tm := NewTemplateManager(baseDir)
rec := httptest.NewRecorder()
if err := tm.Render(rec, "index", nil); err == nil {
t.Fatalf("expected error for missing template")
}
}
func writeTemplate(t *testing.T, dir, name, contents string) {
t.Helper()
path := filepath.Join(dir, name)
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
t.Fatalf("write template %s: %v", name, err)
}
}

View File

@ -0,0 +1,234 @@
package web
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"donniemarko/internal/note"
"donniemarko/internal/render"
"donniemarko/internal/service"
"donniemarko/internal/storage"
)
type testEnv struct {
handler *Handler
notes map[string]*note.Note
}
func newTestEnv(t *testing.T) *testEnv {
t.Helper()
ns := storage.NewNoteStorage()
notes := []*note.Note{
{
ID: "a1",
Title: "Alpha",
Content: "# Alpha\ncontent",
UpdatedAt: time.Date(2025, 12, 31, 10, 0, 0, 0, time.UTC),
},
{
ID: "b2",
Title: "Beta",
Content: "# Beta\nRust tips",
UpdatedAt: time.Date(2026, 1, 1, 10, 0, 0, 0, time.UTC),
},
{
ID: "z9",
Title: "Zulu",
Content: "# Zulu\nRust book",
UpdatedAt: time.Date(2026, 1, 2, 10, 0, 0, 0, time.UTC),
},
}
for _, n := range notes {
if err := ns.Create(n); err != nil {
t.Fatalf("create note: %v", err)
}
}
svc := service.NewNoteService()
svc.SetStorage(ns)
tm := render.NewTemplateManager("templates")
handler := NewHandler(svc, tm)
noteMap := map[string]*note.Note{}
for _, n := range notes {
noteMap[n.ID] = n
}
return &testEnv{
handler: handler,
notes: noteMap,
}
}
func assertOrder(t *testing.T, body string, want []string) {
t.Helper()
prev := -1
for _, title := range want {
idx := strings.Index(body, title)
if idx == -1 {
t.Fatalf("expected to find %q in response body", title)
}
if idx <= prev {
t.Fatalf("expected %q to appear after previous title", title)
}
prev = idx
}
}
func noteMarker(id string) string {
return `data-hash="` + id + `"`
}
func TestHandlerRoot_DefaultSortRecent(t *testing.T) {
env := newTestEnv(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
env.handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
if ct := rec.Header().Get("Content-Type"); ct != "text/html; charset=utf-8" {
t.Fatalf("expected content-type text/html; charset=utf-8, got %q", ct)
}
body := rec.Body.String()
assertOrder(t, body, []string{noteMarker("z9"), noteMarker("b2"), noteMarker("a1")})
}
func TestHandlerRoot_SortAlpha(t *testing.T) {
env := newTestEnv(t)
req := httptest.NewRequest(http.MethodGet, "/?sort=alpha", nil)
rec := httptest.NewRecorder()
env.handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
assertOrder(t, body, []string{noteMarker("a1"), noteMarker("b2"), noteMarker("z9")})
}
func TestHandlerRoot_Search(t *testing.T) {
env := newTestEnv(t)
req := httptest.NewRequest(http.MethodGet, "/?search=rust&sort=alpha", nil)
rec := httptest.NewRecorder()
env.handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
if !strings.Contains(body, "Matching results for query 'rust'") {
t.Fatalf("expected search header to be rendered")
}
if strings.Contains(body, `data-hash="a1"`) {
t.Fatalf("expected non-matching note to be excluded")
}
assertOrder(t, body, []string{noteMarker("b2"), noteMarker("z9")})
}
func TestHandlerNotes_Success(t *testing.T) {
env := newTestEnv(t)
req := httptest.NewRequest(http.MethodGet, "/notes/b2", nil)
rec := httptest.NewRecorder()
env.handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
if !strings.Contains(body, "<h1>Beta</h1>") {
t.Fatalf("expected rendered markdown for note content")
}
if !strings.Contains(body, `class="active-note"`) {
t.Fatalf("expected active note class in list")
}
if !strings.Contains(body, `data-hash="b2"`) {
t.Fatalf("expected active note hash in list")
}
}
func TestHandlerNotes_NotFound(t *testing.T) {
env := newTestEnv(t)
req := httptest.NewRequest(http.MethodGet, "/notes/missing", nil)
rec := httptest.NewRecorder()
env.handler.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("expected status 404, got %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "Note not found") {
t.Fatalf("expected not found message")
}
}
func TestHandler_NotFoundPath(t *testing.T) {
env := newTestEnv(t)
req := httptest.NewRequest(http.MethodGet, "/nope", nil)
rec := httptest.NewRecorder()
env.handler.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("expected status 404, got %d", rec.Code)
}
}
func TestExtractHash(t *testing.T) {
cases := []struct {
name string
path string
want string
}{
{
name: "valid note path",
path: "/notes/abcd",
want: "abcd",
},
{
name: "missing hash",
path: "/notes/",
want: "",
},
{
name: "wrong prefix",
path: "/other/abcd",
want: "",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := extractHash(tc.path); got != tc.want {
t.Fatalf("expected %q, got %q", tc.want, got)
}
})
}
}