Diskussionsketten: jüngste Antwort im Teilbaum an Feed- und Antwortkarten
Jede Karte in Feed und Antwortliste zeigt die Antwort, die ihre Diskussion zuletzt fortgesetzt hat: [Beitrag] -- "k weitere Beiträge" -- [Antwort]. Gelöschte Beiträge werden als jüngste übersprungen, zählen auf dem Pfad mit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8e79e94bbb
commit
ade853f560
@@ -314,6 +314,10 @@ type threadEntry struct {
|
||||
Deleted int64 `json:"deleted"`
|
||||
Content string `json:"content"`
|
||||
Username string `json:"username"`
|
||||
Latest *struct {
|
||||
Entry threadEntry `json:"entry"`
|
||||
Skipped int64 `json:"skipped"`
|
||||
} `json:"latest"`
|
||||
}
|
||||
|
||||
// threadJSON ist die Antwortform von /entry/{pid}/thread. Nicht zu verwechseln
|
||||
@@ -438,6 +442,64 @@ func TestEditEntry(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// latestOf beschreibt Latest eines Beitrags knapp für Vergleiche und
|
||||
// Fehlermeldungen: "pid/skipped" oder "-".
|
||||
func latestOf(e threadEntry) string {
|
||||
if e.Latest == nil {
|
||||
return "-"
|
||||
}
|
||||
return fmt.Sprintf("%d/%d", e.Latest.Entry.PID, e.Latest.Skipped)
|
||||
}
|
||||
|
||||
func TestLatestDescendant(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
alice := registerAndLogin(t, srv, "alice")
|
||||
|
||||
// root ── a ── b ── c
|
||||
// └─ d (d vor b und c erstellt)
|
||||
root := createEntry(t, alice, srv, "wurzel")
|
||||
a := createReply(t, alice, srv, "a", root)
|
||||
d := createReply(t, alice, srv, "d", root)
|
||||
b := createReply(t, alice, srv, "b", a)
|
||||
c := createReply(t, alice, srv, "c", b)
|
||||
|
||||
feed := func() []threadEntry {
|
||||
resp, err := http.Get(srv.URL + "/api/entry/feed/0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var f []threadEntry
|
||||
json.NewDecoder(resp.Body).Decode(&f)
|
||||
return f
|
||||
}
|
||||
|
||||
// Feed: Root mit c, dazwischen a und b.
|
||||
if f := feed(); len(f) != 1 || latestOf(f[0]) != fmt.Sprintf("%d/2", c) {
|
||||
t.Fatalf("feed latest: erwartet %d/2, bekam %s", c, latestOf(f[0]))
|
||||
}
|
||||
|
||||
// Antworten auf Root: a mit c (b dazwischen), d ohne Nachfahren.
|
||||
want := map[int64]string{a: fmt.Sprintf("%d/1", c), d: "-"}
|
||||
for _, r := range getThread(t, srv, root).Replies {
|
||||
if got := latestOf(r); got != want[r.PID] {
|
||||
t.Fatalf("reply %d latest: erwartet %s, bekam %s", r.PID, want[r.PID], got)
|
||||
}
|
||||
}
|
||||
|
||||
// Direkte Antwort: skipped 0.
|
||||
if r := getThread(t, srv, a).Replies; len(r) != 1 || latestOf(r[0]) != fmt.Sprintf("%d/0", c) {
|
||||
t.Fatalf("b latest: erwartet %d/0, bekam %+v", c, r)
|
||||
}
|
||||
|
||||
// Gelöschter jüngster Beitrag wird übersprungen -> nächstjüngerer (b).
|
||||
resp := postForm(t, alice, fmt.Sprintf("%s/api/entry/%d/delete", srv.URL, c), url.Values{})
|
||||
resp.Body.Close()
|
||||
if f := feed(); latestOf(f[0]) != fmt.Sprintf("%d/1", b) {
|
||||
t.Fatalf("feed latest nach delete: erwartet %d/1, bekam %s", b, latestOf(f[0]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSoftDelete(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
alice := registerAndLogin(t, srv, "alice")
|
||||
|
||||
Reference in New Issue
Block a user