build and ready for demo

login screen
This commit is contained in:
Daniel Noulet 2026-09-19 17:33:44 +02:00
parent 0ae2dc3888
commit 6af2853faa
33 changed files with 2020 additions and 1221 deletions

View file

@ -0,0 +1,127 @@
package service
import (
"context"
"path/filepath"
"testing"
)
func TestSeedStoreListsNewestFirst(t *testing.T) {
s, err := NewContentService(SeedContent(), "")
if err != nil {
t.Fatal(err)
}
items, err := s.List(context.Background(), 5, 0, "", "", nil)
if err != nil {
t.Fatal(err)
}
if len(items) != 5 {
t.Fatalf("limit ignored: got %d items", len(items))
}
if items[0].Slug != "built-with-ai" {
t.Fatalf("newest item = %q, want built-with-ai", items[0].Slug)
}
if !items[0].UpdatedAt.After(items[1].UpdatedAt) && !items[0].UpdatedAt.Equal(items[1].UpdatedAt) {
t.Fatalf("items are not ordered newest first: %v then %v", items[0].UpdatedAt, items[1].UpdatedAt)
}
}
func TestListFiltersByCategoryAndBadge(t *testing.T) {
s, err := NewContentService(SeedContent(), "")
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
categories, err := s.ListCategories(ctx)
if err != nil || len(categories) == 0 {
t.Fatalf("categories = %v err = %v", categories, err)
}
byCategory, err := s.List(ctx, 100, 0, "linux", "", nil)
if err != nil || len(byCategory) == 0 {
t.Fatalf("linux category returned %d items (err %v)", len(byCategory), err)
}
for _, item := range byCategory {
if item.Category != "linux" {
t.Fatalf("category filter leaked %q", item.Category)
}
}
// Badges combine with AND semantics, so this pair must be narrower than one.
tagged, err := s.List(ctx, 100, 0, "", "", []string{"tooling"})
if err != nil || len(tagged) < 2 {
t.Fatalf("tooling badge returned %d items (err %v)", len(tagged), err)
}
both, err := s.List(ctx, 100, 0, "", "", []string{"tooling", "docker"})
if err != nil {
t.Fatal(err)
}
if len(both) == 0 || len(both) >= len(tagged) {
t.Fatalf("badge AND filtering wrong: tooling=%d tooling+docker=%d", len(tagged), len(both))
}
for _, item := range both {
if !contains(item.Badges, "tooling") || !contains(item.Badges, "docker") {
t.Fatalf("badge filter leaked %v", item.Badges)
}
}
}
func contains(values []string, want string) bool {
for _, value := range values {
if value == want {
return true
}
}
return false
}
func TestUpdatePersistsAcrossRestart(t *testing.T) {
file := filepath.Join(t.TempDir(), "content.json")
s, err := NewContentService(SeedContent(), file)
if err != nil {
t.Fatal(err)
}
saved, err := s.Update(context.Background(), Content{
Slug: "built-with-ai",
Title: "Edited title",
Body: "Edited body",
Status: "draft",
Badges: []string{"Go", "go", "writing"},
})
if err != nil {
t.Fatal(err)
}
if saved.Title != "Edited title" || saved.Published {
t.Fatalf("unexpected save result: %+v", saved)
}
if len(saved.Badges) != 2 {
t.Fatalf("badges were not normalised and deduplicated: %v", saved.Badges)
}
// A new service pointed at the same file must load the edit, not the seed.
restarted, err := NewContentService(SeedContent(), file)
if err != nil {
t.Fatal(err)
}
reloaded, err := restarted.Get(context.Background(), "built-with-ai")
if err != nil {
t.Fatal(err)
}
if reloaded.Title != "Edited title" {
t.Fatalf("edit did not survive restart: %q", reloaded.Title)
}
}
func TestUpdateRejectsInvalidContent(t *testing.T) {
s, err := NewContentService(SeedContent(), "")
if err != nil {
t.Fatal(err)
}
if _, err = s.Update(context.Background(), Content{Slug: "missing", Title: "T", Body: "B"}); err == nil {
t.Fatal("expected unknown slug to be rejected")
}
if _, err = s.Update(context.Background(), Content{Slug: "built-with-ai", Title: "", Body: "B"}); err == nil {
t.Fatal("expected empty title to be rejected")
}
}