feat(release): v0.1.0
commit06ed2c3cbeAuthor: adminoo <git@kadath.corp> Date: Tue Feb 3 11:34:24 2026 +0100 fix: changed detected by scanner but no updated by render layer commit01dcaf882aAuthor: adminoo <git@kadath.corp> Date: Tue Feb 3 10:19:05 2026 +0100 feat: VERSION bumb commit229223f77aAuthor: adminoo <git@kadath.corp> Date: Tue Feb 3 09:53:08 2026 +0100 feat: filter and search by tag commitcb11e34798Author: adminoo <git@kadath.corp> Date: Tue Feb 3 09:41:03 2026 +0100 feat: tag system commit3f5cf0d673Author: adminoo <git@kadath.corp> Date: Tue Feb 3 09:15:29 2026 +0100 feat: sqlite storage draft commitd6617cec02Author: adminoo <git@kadath.corp> Date: Tue Feb 3 09:04:11 2026 +0100 feat: metadata draft commit7238d02a13Author: adminoo <git@kadath.corp> Date: Mon Feb 2 10:18:42 2026 +0100 fix: body overflowing commit16ff836274Author: adminoo <git@kadath.corp> Date: Mon Feb 2 10:09:01 2026 +0100 feat: tests for http handlers and render package commit36ac3f03aaAuthor: adminoo <git@kadath.corp> Date: Mon Feb 2 09:45:29 2026 +0100 feat: Dark theme, placeholder metadata panel commite6923fa4f5Author: adminoo <git@kadath.corp> Date: Sun Feb 1 18:26:59 2026 +0100 fix: uneeded func + uneeded bogus note creation logic commit4458ba2d15Author: adminoo <git@kadath.corp> Date: Sun Feb 1 18:26:21 2026 +0100 feat: log when changing note states commit92a6f84540Author: adminoo <git@kadath.corp> Date: Sun Feb 1 16:55:40 2026 +0100 possibly first working draft commite27aadc603Author: adminoo <git@kadath.corp> Date: Sun Feb 1 11:55:16 2026 +0100 draft shits
This commit is contained in:
307
internal/web/handler_test.go
Normal file
307
internal/web/handler_test.go
Normal file
@ -0,0 +1,307 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"donniemarko/internal/note"
|
||||
"donniemarko/internal/render"
|
||||
"donniemarko/internal/service"
|
||||
"donniemarko/internal/storage"
|
||||
)
|
||||
|
||||
type testEnv struct {
|
||||
handler *Handler
|
||||
storage *storage.NoteStorage
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return &testEnv{
|
||||
handler: handler,
|
||||
storage: ns,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTags_Add(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
|
||||
form := url.Values{}
|
||||
form.Set("tag", " Go ")
|
||||
req := httptest.NewRequest(http.MethodPost, "/notes/b2/tags", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
env.handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusSeeOther {
|
||||
t.Fatalf("expected status 303, got %d", rec.Code)
|
||||
}
|
||||
|
||||
n, err := env.storage.Get("b2")
|
||||
if err != nil {
|
||||
t.Fatalf("get note: %v", err)
|
||||
}
|
||||
if len(n.Tags) != 1 || n.Tags[0] != "go" {
|
||||
t.Fatalf("expected normalized tag on note, got %+v", n.Tags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerTags_Remove(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
|
||||
if err := env.storage.AddTag("b2", "go"); err != nil {
|
||||
t.Fatalf("seed tag: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/notes/b2/tags/go", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
env.handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusSeeOther {
|
||||
t.Fatalf("expected status 303, got %d", rec.Code)
|
||||
}
|
||||
|
||||
n, err := env.storage.Get("b2")
|
||||
if err != nil {
|
||||
t.Fatalf("get note: %v", err)
|
||||
}
|
||||
if len(n.Tags) != 0 {
|
||||
t.Fatalf("expected tag to be removed, got %+v", n.Tags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRoot_TagFilter(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
|
||||
if err := env.storage.AddTag("a1", "go"); err != nil {
|
||||
t.Fatalf("seed tag: %v", err)
|
||||
}
|
||||
if err := env.storage.AddTag("b2", "rust"); err != nil {
|
||||
t.Fatalf("seed tag: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/?tag=go", 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, noteMarker("b2")) {
|
||||
t.Fatalf("expected non-matching note to be excluded")
|
||||
}
|
||||
if !strings.Contains(body, noteMarker("a1")) {
|
||||
t.Fatalf("expected matching note to be included")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user