Federated via graft
0

Configure Feed

Select the types of activity you want to include in your feed.

graft-source / internal / status / status_test.go
4.3 kB 118 lines
1package status 2 3import ( 4 "testing" 5 "time" 6 7 "graft/internal/state" 8) 9 10func mkEntry(repoPair, series, kind, url, summary string, t time.Time) state.ActivityEntry { 11 return state.ActivityEntry{ 12 Activity: state.Activity{ 13 RepoPair: repoPair, 14 Series: series, 15 Kind: kind, 16 URL: url, 17 Summary: summary, 18 }, 19 OccurredAt: t, 20 } 21} 22 23// TestBuildSeriesRowsAlignsHealthySides verifies the case this test exists 24// for: two sides that actually hold the same content, but where graft only 25// happened to log the push under one of them, must render the exact same 26// number of cells — a real one on the side that logged it, a synthetic one 27// (visually identical, only the tooltip differs) on the side that didn't. 28func TestBuildSeriesRowsAlignsHealthySides(t *testing.T) { 29 base := time.Date(2026, 9, 13, 12, 0, 0, 0, time.UTC) 30 // Both commits are only ever logged under the Forgejo side's own host 31 // (f1.example) — mirroring the real-world case that motivated this 32 // fix, where the sync code happens to log an event's URL against one 33 // particular host even though the content lands everywhere. 34 entries := []state.ActivityEntry{ 35 mkEntry("x-f1", "x", "git", "https://f1.example/x/commit/aaa", "commit aaa", base), 36 mkEntry("x-f1", "x", "git", "https://f1.example/x/commit/bbb", "commit bbb", base.Add(time.Minute)), 37 } 38 topology := map[string][]ServerRef{ 39 "x": { 40 {Label: "f1.example", URL: "https://f1.example/x", PairName: "x-f1"}, 41 {Label: "r1.example", URL: "https://r1.example/x", Radicle: true, PairName: "x-f1"}, 42 }, 43 } 44 health := map[string]bool{"x": true} 45 46 rows := buildSeriesRows(entries, health, topology, map[string]PairResult{}) 47 if len(rows) != 1 { 48 t.Fatalf("expected 1 series row, got %d", len(rows)) 49 } 50 sub := map[string]subRow{} 51 for _, s := range rows[0].SubRows { 52 sub[s.Label] = s 53 } 54 f1, r1 := sub["f1.example"], sub["r1.example"] 55 if len(f1.Events) != len(r1.Events) { 56 t.Fatalf("expected aligned cell counts, got f1=%d r1=%d", len(f1.Events), len(r1.Events)) 57 } 58 if len(f1.Events) != 2 { 59 t.Fatalf("expected 2 cells per side, got %d", len(f1.Events)) 60 } 61 // f1 logged both itself: both real (State == ""). 62 for _, e := range f1.Events { 63 if e.State != "" { 64 t.Errorf("expected f1's own events to be real, got State=%q", e.State) 65 } 66 } 67 // r1 never logged either directly: both synthetic, marked "replicated" 68 // since it's the Radicle side — but same count, same order, so the 69 // two rows read as aligned rather than one looking "behind". 70 for _, e := range r1.Events { 71 if e.State != "replicated" { 72 t.Errorf("expected r1's filled-in events to be synthetic/replicated, got State=%q", e.State) 73 } 74 } 75} 76 77// TestBuildSeriesRowsSkipsFillOnError verifies the other half: a side whose 78// last sync pass actually failed must NOT get the synthetic alignment fill 79// — it should show only what it has confirmed itself, plus a non-empty 80// subRow.Error, so a real problem stays visible instead of being papered 81// over by the alignment logic above. 82func TestBuildSeriesRowsSkipsFillOnError(t *testing.T) { 83 base := time.Date(2026, 9, 13, 12, 0, 0, 0, time.UTC) 84 entries := []state.ActivityEntry{ 85 mkEntry("x-f1", "x", "git", "https://f1.example/x/commit/aaa", "commit aaa", base), 86 mkEntry("x-f1", "x", "git", "https://f1.example/x/commit/bbb", "commit bbb", base.Add(time.Minute)), 87 } 88 topology := map[string][]ServerRef{ 89 "x": { 90 {Label: "f1.example", URL: "https://f1.example/x", PairName: "x-f1"}, 91 {Label: "f2.example", URL: "https://f2.example/x", PairName: "x-f2"}, 92 }, 93 } 94 health := map[string]bool{"x": false} 95 pairResults := map[string]PairResult{ 96 "x-f2": {Name: "x-f2", GitError: "read forgejo head: 502 Bad Gateway"}, 97 } 98 99 rows := buildSeriesRows(entries, health, topology, pairResults) 100 sub := map[string]subRow{} 101 for _, s := range rows[0].SubRows { 102 sub[s.Label] = s 103 } 104 f2 := sub["f2.example"] 105 if f2.Error == "" { 106 t.Fatal("expected f2's subRow.Error to be set") 107 } 108 if len(f2.Events) != 0 { 109 t.Fatalf("expected no synthetic fill on an errored side, got %d events", len(f2.Events)) 110 } 111 f1 := sub["f1.example"] 112 if f1.Error != "" { 113 t.Fatalf("expected f1 (healthy) to have no error, got %q", f1.Error) 114 } 115 if len(f1.Events) != 2 { 116 t.Fatalf("expected f1's own 2 real events, got %d", len(f1.Events)) 117 } 118}