197 lines
5.5 KiB
Go
197 lines
5.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// write legt einen Eintrag an und bricht den Test bei einem Fehler ab.
|
|
func write(t *testing.T, s *Store, projectID, slug, content string) {
|
|
t.Helper()
|
|
if _, err := s.PutEntry(context.Background(), EntryWrite{
|
|
ProjectID: projectID, Slug: slug, Title: "Titel " + slug, Content: content,
|
|
Source: SourceHuman, Author: "test",
|
|
}); err != nil {
|
|
t.Fatalf("Eintrag %q: %v", slug, err)
|
|
}
|
|
}
|
|
|
|
// TestRelatedEntries prüft Nachbarn im Verweisgraphen:
|
|
//
|
|
// start → a → fern
|
|
// b → start
|
|
// start → fehlt (Verweis ohne Eintrag)
|
|
// c (unverknüpft)
|
|
func TestRelatedEntries(t *testing.T) {
|
|
s := testStore(t)
|
|
ctx := context.Background()
|
|
p := testProject(t, s)
|
|
|
|
write(t, s, p.ID, "start", "siehe [[a]] und [[fehlt]]")
|
|
write(t, s, p.ID, "a", "weiter bei [[fern]]")
|
|
write(t, s, p.ID, "b", "hängt an [[start]]")
|
|
write(t, s, p.ID, "fern", "Ende")
|
|
write(t, s, p.ID, "c", "allein")
|
|
|
|
related, err := s.RelatedEntries(ctx, p.ID, "start", 2, 20)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
found := make(map[string]RelatedEntry)
|
|
for _, v := range related {
|
|
found[v.Slug] = v
|
|
}
|
|
|
|
want := map[string]struct {
|
|
distance int
|
|
via string
|
|
relation string
|
|
}{
|
|
"a": {1, "start", RelationLinksTo},
|
|
"b": {1, "start", RelationLinkedBy},
|
|
"fern": {2, "a", RelationLinksTo},
|
|
}
|
|
for slug, e := range want {
|
|
v, ok := found[slug]
|
|
if !ok {
|
|
t.Errorf("%q fehlt", slug)
|
|
continue
|
|
}
|
|
if v.Distance != e.distance || v.Via != e.via || v.Relation != e.relation {
|
|
t.Errorf("%q: Abstand %d über %q (%s), erwartet %d über %q (%s)",
|
|
slug, v.Distance, v.Via, v.Relation, e.distance, e.via, e.relation)
|
|
}
|
|
if v.Title != "Titel "+slug || v.UpdatedAt.IsZero() {
|
|
t.Errorf("%q: Titel oder Änderungsdatum fehlen: %+v", slug, v)
|
|
}
|
|
}
|
|
|
|
for _, slug := range []string{"start", "fehlt", "c"} {
|
|
if _, ok := found[slug]; ok {
|
|
t.Errorf("%q darf nicht unter den verwandten Einträgen stehen", slug)
|
|
}
|
|
}
|
|
|
|
// Näheres vor Fernerem.
|
|
if related[len(related)-1].Slug != "fern" {
|
|
t.Errorf("die zweite Reihe steht nicht am Ende: %+v", related)
|
|
}
|
|
|
|
// Mit Abstand 1 nur die direkten Nachbarn.
|
|
direct, err := s.RelatedEntries(ctx, p.ID, "start", 1, 20)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(direct) != 2 {
|
|
t.Errorf("Abstand 1 lieferte %d Einträge, erwartet 2: %+v", len(direct), direct)
|
|
}
|
|
|
|
// Die Obergrenze gilt.
|
|
limited, err := s.RelatedEntries(ctx, p.ID, "start", 2, 1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(limited) != 1 {
|
|
t.Errorf("Limit 1 lieferte %d Einträge", len(limited))
|
|
}
|
|
}
|
|
|
|
// TestRelatedEntriesSkipsTrash prüft, dass gelöschte Einträge weder als
|
|
// Nachbar erscheinen noch als Brücke in die zweite Reihe dienen.
|
|
func TestRelatedEntriesSkipsTrash(t *testing.T) {
|
|
s := testStore(t)
|
|
ctx := context.Background()
|
|
p := testProject(t, s)
|
|
|
|
write(t, s, p.ID, "start", "siehe [[weg]]")
|
|
write(t, s, p.ID, "weg", "siehe [[dahinter]]")
|
|
write(t, s, p.ID, "dahinter", "Ende")
|
|
|
|
if err := s.MoveEntryToTrash(ctx, p.ID, "weg", "test"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
related, err := s.RelatedEntries(ctx, p.ID, "start", 2, 20)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(related) != 0 {
|
|
t.Errorf("über einen gelöschten Eintrag wurde etwas gefunden: %+v", related)
|
|
}
|
|
|
|
// Ein Ausgangseintrag im Papierkorb gilt als nicht vorhanden.
|
|
if _, err := s.RelatedEntries(ctx, p.ID, "weg", 2, 20); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("Fehler = %v, erwartet ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestRelatedEntriesUnknownSlug(t *testing.T) {
|
|
s := testStore(t)
|
|
p := testProject(t, s)
|
|
|
|
if _, err := s.RelatedEntries(context.Background(), p.ID, "gibtesnicht", 2, 20); !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("Fehler = %v, erwartet ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
// TestEntryStale prüft die Grenze für veraltetes Wissen (AUSBAUSTUFEN.md 3.5).
|
|
func TestEntryStale(t *testing.T) {
|
|
now := time.Date(2026, 9, 25, 12, 0, 0, 0, time.UTC)
|
|
|
|
cases := []struct {
|
|
name string
|
|
changed time.Time
|
|
want bool
|
|
}{
|
|
{"gestern", now.AddDate(0, 0, -1), false},
|
|
{"vor elf Monaten", now.AddDate(0, -11, 0), false},
|
|
{"vor zwei Jahren", now.AddDate(-2, 0, 0), true},
|
|
{"ohne Datum", time.Time{}, false},
|
|
}
|
|
for _, f := range cases {
|
|
t.Run(f.name, func(t *testing.T) {
|
|
if got := (Entry{UpdatedAt: f.changed}).Stale(now); got != f.want {
|
|
t.Errorf("Stale = %v, erwartet %v", got, f.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPutEntryKeepsImportedTimestamps prüft, dass vorgegebene Zeitstempel
|
|
// beim Anlegen übernommen und beim Ändern ignoriert werden.
|
|
func TestPutEntryKeepsImportedTimestamps(t *testing.T) {
|
|
s := testStore(t)
|
|
ctx := context.Background()
|
|
p := testProject(t, s)
|
|
|
|
old := time.Date(2023, 3, 11, 9, 0, 0, 0, time.UTC)
|
|
e, err := s.PutEntry(ctx, EntryWrite{
|
|
ProjectID: p.ID, Slug: "alt", Title: "Alt", Content: "x",
|
|
Source: SourceHuman, CreatedAt: old, UpdatedAt: old,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
read, _ := s.EntryBySlug(ctx, p.ID, "alt")
|
|
if !read.CreatedAt.Equal(old) || !read.UpdatedAt.Equal(old) || !e.UpdatedAt.Equal(old) {
|
|
t.Errorf("Zeitstempel nicht übernommen: angelegt %v, geändert %v", read.CreatedAt, read.UpdatedAt)
|
|
}
|
|
|
|
// Eine Änderung passiert jetzt, egal was mitgegeben wird.
|
|
if _, err := s.PutEntry(ctx, EntryWrite{
|
|
ProjectID: p.ID, Slug: "alt", Title: "Alt", Content: "neu",
|
|
Source: SourceHuman, UpdatedAt: old,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
read, _ = s.EntryBySlug(ctx, p.ID, "alt")
|
|
if read.UpdatedAt.Equal(old) {
|
|
t.Error("beim Ändern wurde der vorgegebene Zeitstempel übernommen")
|
|
}
|
|
if !read.CreatedAt.Equal(old) {
|
|
t.Error("beim Ändern ging das Anlegedatum verloren")
|
|
}
|
|
}
|