189 lines
5.9 KiB
Go
189 lines
5.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Tests der Kennzahlen für die Systemseite (systemstand.go). Laufen mit
|
|
// MUNIN_TEST_PG auch gegen PostgreSQL (siehe testStore).
|
|
|
|
// TestBestand zählt Projekte, Einträge, Papierkorb, Fassungen und Keys —
|
|
// Keys nach denselben Regeln wie die Seite "Zugänge".
|
|
func TestBestand(t *testing.T) {
|
|
s := testStore(t)
|
|
ctx := context.Background()
|
|
jetzt := time.Now()
|
|
|
|
leer, err := s.Bestand(ctx, jetzt)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if leer != (Bestand{}) {
|
|
t.Errorf("leerer Workspace: %+v, erwartet nur Nullen", leer)
|
|
}
|
|
|
|
p := testProject(t, s)
|
|
for _, slug := range []string{"a", "b", "c"} {
|
|
if _, err := s.PutEntry(ctx, EntryWrite{ProjectID: p.ID, Slug: slug, Title: slug, Content: "x", Source: SourceHuman}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// Eine Änderung legt eine ältere Fassung ab.
|
|
if _, err := s.PutEntry(ctx, EntryWrite{ProjectID: p.ID, Slug: "a", Title: "a", Content: "neu", Source: SourceHuman}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.MoveEntryToTrash(ctx, p.ID, "c", "test"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Keys: einer frisch, einer läuft bald ab, einer zurückgezogen, einer
|
|
// abgelaufen. Gültig sind die ersten beiden.
|
|
if _, err := s.CreateAPIKey(ctx, p.ID, "frisch", "h1", "write"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.CreateAPIKeyWithExpiry(ctx, p.ID, "bald", "h2", "read", jetzt.Add(3*24*time.Hour)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
weg, err := s.CreateAPIKey(ctx, p.ID, "weg", "h3", "read")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.RevokeAPIKey(ctx, weg.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.CreateAPIKeyWithExpiry(ctx, p.ID, "abgelaufen", "h4", "read", jetzt.Add(time.Hour)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Zwei Stunden später: "abgelaufen" ist es dann.
|
|
b, err := s.Bestand(ctx, jetzt.Add(2*time.Hour))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
erwartet := Bestand{Projekte: 1, Eintraege: 2, Fassungen: 1, Papierkorb: 1, Keys: 2, KeysLaufenAb: 1}
|
|
if b != erwartet {
|
|
t.Errorf("Bestand = %+v\nerwartet %+v", b, erwartet)
|
|
}
|
|
|
|
// 100 Tage später ist "frisch" nie benutzt worden und damit ungenutzt;
|
|
// "bald" ist bis dahin abgelaufen und zählt gar nicht mehr.
|
|
b, err = s.Bestand(ctx, jetzt.Add(100*24*time.Hour))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if b.Keys != 1 || b.KeysUngenutzt != 1 || b.KeysLaufenAb != 0 {
|
|
t.Errorf("nach 100 Tagen: Keys %d, ungenutzt %d, laufen ab %d — erwartet 1, 1, 0", b.Keys, b.KeysUngenutzt, b.KeysLaufenAb)
|
|
}
|
|
}
|
|
|
|
// TestBelegung: Ein Workspace mit Inhalt belegt Platz. Wie viel genau, hängt
|
|
// von der Datenbank ab — geprüft wird nur, dass die Zahl plausibel ist und
|
|
// mit dem Inhalt wächst.
|
|
func TestBelegung(t *testing.T) {
|
|
s := testStore(t)
|
|
ctx := context.Background()
|
|
|
|
vorher, err := s.Belegung(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if vorher.Daten <= 0 {
|
|
t.Fatalf("leerer Workspace belegt %d Bytes — die Tabellen allein brauchen Platz", vorher.Daten)
|
|
}
|
|
|
|
p := testProject(t, s)
|
|
inhalt := make([]byte, 64<<10)
|
|
for i := range inhalt {
|
|
inhalt[i] = 'a' + byte(i%26)
|
|
}
|
|
for _, slug := range []string{"gross-1", "gross-2", "gross-3", "gross-4"} {
|
|
if _, err := s.PutEntry(ctx, EntryWrite{ProjectID: p.ID, Slug: slug, Title: slug, Content: string(inhalt), Source: SourceHuman}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
nachher, err := s.Belegung(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Bei SQLite landet das Geschriebene zunächst in der WAL-Datei, bei
|
|
// PostgreSQL in den Tabellen. Zusammen muss es mehr geworden sein.
|
|
if nachher.Gesamt() <= vorher.Gesamt() {
|
|
t.Errorf("Belegung vorher %d, nachher %d — sollte gewachsen sein", vorher.Gesamt(), nachher.Gesamt())
|
|
}
|
|
}
|
|
|
|
// TestAktivitaet zählt Schreibvorgänge je Projekt, Tag und Weg sowie Suchen,
|
|
// und lässt alles vor der Grenze weg.
|
|
func TestAktivitaet(t *testing.T) {
|
|
s := testStore(t)
|
|
ctx := context.Background()
|
|
p := testProject(t, s)
|
|
|
|
// Mit festen Zeitstempeln direkt eintragen — LogAudit und LogSearch
|
|
// nehmen immer "jetzt".
|
|
audit := func(ts, aktion, quelle string) {
|
|
t.Helper()
|
|
_, err := s.schreiben.ExecContext(ctx,
|
|
`INSERT INTO audit_events (id, ts, project_id, actor, action, source) VALUES (?, ?, ?, 'test', ?, ?)`,
|
|
NewID(), ts, p.ID, aktion, quelle)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
suche := func(ts string) {
|
|
t.Helper()
|
|
_, err := s.schreiben.ExecContext(ctx,
|
|
`INSERT INTO search_log (ts, project_id, query, results) VALUES (?, ?, 'frage', 1)`, ts, p.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
audit("2026-10-01T08:00:00Z", ActionEntryWrite, SourceMCP)
|
|
audit("2026-10-01T09:00:00Z", ActionEntryWrite, SourceMCP)
|
|
audit("2026-10-01T10:00:00Z", ActionEntryDelete, SourceWeb)
|
|
audit("2026-10-01T11:00:00Z", ActionLoginSuccess, SourceWeb) // kein Schreibvorgang
|
|
audit("2026-10-02T08:00:00Z", ActionEntryWrite, SourceWeb)
|
|
audit("2026-09-20T08:00:00Z", ActionEntryWrite, SourceMCP) // vor der Grenze
|
|
suche("2026-10-02T12:00:00Z")
|
|
suche("2026-10-02T13:00:00Z")
|
|
suche("2026-09-20T13:00:00Z") // vor der Grenze
|
|
|
|
liste, err := s.Aktivitaet(ctx, time.Date(2026, 9, 25, 0, 0, 0, 0, time.UTC))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
je := map[string]Tagesaktivitaet{}
|
|
for _, a := range liste {
|
|
if a.ProjektID != p.ID {
|
|
t.Errorf("fremdes Projekt %q", a.ProjektID)
|
|
}
|
|
je[a.Tag] = a
|
|
}
|
|
if len(je) != 2 {
|
|
t.Fatalf("%d Tage, erwartet 2: %+v", len(je), liste)
|
|
}
|
|
if a := je["2026-10-01"]; a.SchreibenAgent != 2 || a.SchreibenMensch != 1 || a.Suchen != 0 {
|
|
t.Errorf("1.10.: %+v, erwartet 2 Agent, 1 Mensch, 0 Suchen", a)
|
|
}
|
|
if a := je["2026-10-02"]; a.SchreibenAgent != 0 || a.SchreibenMensch != 1 || a.Suchen != 2 {
|
|
t.Errorf("2.10.: %+v, erwartet 0 Agent, 1 Mensch, 2 Suchen", a)
|
|
}
|
|
}
|
|
|
|
// TestSchemastand: Nach dem Öffnen steht der Workspace auf der neuesten
|
|
// Migration, die das Programm mitbringt.
|
|
func TestSchemastand(t *testing.T) {
|
|
s := testStore(t)
|
|
ist, soll, err := s.Schemastand(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if soll == "" || ist != soll {
|
|
t.Errorf("Schemastand ist %q, soll %q", ist, soll)
|
|
}
|
|
}
|