v2.0.0: claims model - signed MMUR2 votes in a truth DB, merged view in a rebuildable cache DB; POST /api/claims, claims and merge routes, unsigned writes answer 410
Build and Push Docker Image / build (push) Successful in 5m1s

This commit is contained in:
Worph
2026-10-25 17:36:00 +00:00
parent b0adefb97b
commit 84af298792
78 changed files with 7203 additions and 5617 deletions
+44 -15
View File
@@ -33,11 +33,11 @@ func (r *syncRecorder) body() string {
func TestMetaEventsSnapshotThenTail(t *testing.T) {
stor := newTestStorage(t)
s := &Server{storage: stor}
for i, id := range []string{"R1", "R2"} {
if err := stor.SetMetadataFlat(id, map[string]string{"title": "T", "n": string(rune('a' + i))}); err != nil {
t.Fatal(err)
}
a := testSigner(t, 1)
for i, cid := range []string{"bafyR1", "bafyR2"} {
storeClaims(t, stor, signed(t, a, cid, "title", "T", 1), signed(t, a, cid, "n", string(rune('a'+i)), 1))
}
r1, r2 := stor.ResolveRoot("bafyR1"), stor.ResolveRoot("bafyR2")
ctx, cancel := context.WithCancel(context.Background())
req := httptest.NewRequest(http.MethodGet, "/api/events/meta", nil).WithContext(ctx)
@@ -56,34 +56,63 @@ func TestMetaEventsSnapshotThenTail(t *testing.T) {
time.Sleep(20 * time.Millisecond)
}
}
waitFor(`"key":"file:R2/title"`)
if err := stor.SetProperty("R1", "title", "changed"); err != nil {
t.Fatal(err)
}
waitFor(`"key":"file:` + r2 + `/title"`)
storeClaims(t, stor, signed(t, a, "bafyR1", "title", "changed", 2))
waitFor(`event: set`)
time.Sleep(300 * time.Millisecond)
cancel()
<-done
body := rec.body()
for _, k := range []string{"file:R1/title", "file:R1/n", "file:R2/title", "file:R2/n"} {
keys := []string{"file:" + r1 + "/title", "file:" + r1 + "/n", "file:" + r2 + "/title", "file:" + r2 + "/n",
"file:" + r1 + "/cids/bafyR1", "file:" + r2 + "/cids/bafyR2"}
for _, k := range keys {
if !strings.Contains(body, `"key":"`+k+`"`) {
t.Errorf("snapshot is missing %s", k)
}
}
events := strings.Split(strings.TrimSpace(body), "\n\n")
if len(events) != 5 {
t.Fatalf("want 4 snapshot events + 1 live event, got %d:\n%s", len(events), body)
if len(events) != 7 {
t.Fatalf("want 6 snapshot events + 1 live event, got %d:\n%s", len(events), body)
}
for i, e := range events[:3] {
for i, e := range events[:5] {
if strings.Contains(e, "id: ") {
t.Errorf("snapshot event %d carries an id: %q", i, e)
}
}
if !strings.HasPrefix(events[3], "id: ") || !strings.HasPrefix(events[4], "id: ") {
if !strings.HasPrefix(events[5], "id: ") || !strings.HasPrefix(events[6], "id: ") {
t.Fatalf("the last snapshot event and the live event must carry ids:\n%s", body)
}
if !strings.Contains(events[4], `"key":"file:R1/title"`) {
t.Fatalf("live tail did not follow the snapshot: %q", events[4])
if !strings.Contains(events[6], `"key":"file:`+r1+`/title"`) {
t.Fatalf("live tail did not follow the snapshot: %q", events[6])
}
}
// A cursor older than the last cache rebuild gets `gap` (reason rebuild): the
// rebuild published no per-key events, so the consumer must re-snapshot.
func TestMetaEventsGapAfterRebuild(t *testing.T) {
stor := newTestStorage(t)
s := &Server{storage: stor}
storeClaims(t, stor, signed(t, testSigner(t, 1), "bafyA", "title", "T", 1))
if err := stor.Rebuild(); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
req := httptest.NewRequest(http.MethodGet, "/api/events/meta", nil).WithContext(ctx)
req.Header.Set("Last-Event-ID", "1000-0")
rec := &syncRecorder{ResponseRecorder: httptest.NewRecorder()}
done := make(chan struct{})
go func() { s.handleEventsMeta(rec, req); close(done) }()
deadline := time.Now().Add(5 * time.Second)
for !strings.Contains(rec.body(), "event: gap") {
if time.Now().After(deadline) {
t.Fatalf("no gap after rebuild:\n%s", rec.body())
}
time.Sleep(20 * time.Millisecond)
}
cancel()
<-done
if !strings.Contains(rec.body(), `"reason":"rebuild"`) {
t.Fatalf("gap reason: %s", rec.body())
}
}