build and ready for demo
login screen
This commit is contained in:
parent
0ae2dc3888
commit
6af2853faa
33 changed files with 2020 additions and 1221 deletions
127
internal/service/content_test.go
Normal file
127
internal/service/content_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue