oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1package slides
2
3import (
4 "context"
5 "fmt"
6 "testing"
7)
8
9// TestSnapshotWrittenAtBoundaryAndUsedOnOpen: a deck that crosses the
10// cadence boundary gets exactly one snapshot, openDeck replays only the
11// tail after it, and the document it produces is the same document the
12// whole log produces.
13//
14// The last clause is the one that matters. A snapshot is an accelerator
15// and derived data (models.go's DeckSnapshot); the moment it is not
16// byte-for-byte the same deck, it is a second, wrong copy of the truth.
17func TestSnapshotWrittenAtBoundaryAndUsedOnOpen(t *testing.T) {
18 d := testDB(t)
19 a, _, err := newApp(d, Config{Env: EnvDevelopment, Logger: quietLogger()})
20 if err != nil {
21 t.Fatalf("newApp: %v", err)
22 }
23 t.Cleanup(func() { a.Close() })
24 ctx := context.Background()
25
26 owner, _, err := ensureUser(ctx, d.G, "paul@example.test")
27 if err != nil {
28 t.Fatalf("ensureUser: %v", err)
29 }
30 actor := actorFor(owner)
31
32 row, err := a.createDeckRow(ctx, owner, "Long deck", defaultTheme)
33 if err != nil {
34 t.Fatalf("createDeckRow: %v", err)
35 }
36 opening, err := openingOps("Long deck", defaultTheme)
37 if err != nil {
38 t.Fatalf("openingOps: %v", err)
39 }
40 if err := a.editDeck(ctx, &row, actor, opening, nil); err != nil {
41 t.Fatalf("editDeck: %v", err)
42 }
43
44 // One handle for the rest, so this is 200 applies rather than 200
45 // full replays — the test is about the boundary, not about how
46 // slowly a deck can be built.
47 h, _, err := a.openDeck(ctx, row)
48 if err != nil {
49 t.Fatalf("openDeck: %v", err)
50 }
51 defer h.Close(ctx)
52 for row.Seq < snapshotCadence+1 {
53 batch, err := opsJSON([]op{{"op": "set_meta", "title": fmt.Sprintf("Long deck %d", row.Seq)}})
54 if err != nil {
55 t.Fatalf("opsJSON: %v", err)
56 }
57 if _, err := a.applyOps(ctx, h, &row, actor, batch, nil); err != nil {
58 t.Fatalf("applyOps at seq %d: %v", row.Seq, err)
59 }
60 }
61 if row.Seq != snapshotCadence+1 {
62 t.Fatalf("the deck is at seq %d, want %d", row.Seq, snapshotCadence+1)
63 }
64
65 var snapshots []DeckSnapshot
66 if err := d.G.WithContext(ctx).Where("deck_id = ?", row.ID).Order("seq").Find(&snapshots).Error; err != nil {
67 t.Fatalf("read snapshots: %v", err)
68 }
69 if len(snapshots) != 1 {
70 t.Fatalf("%d snapshots after %d updates, want exactly 1", len(snapshots), row.Seq)
71 }
72 if snapshots[0].Seq != snapshotCadence {
73 t.Errorf("the snapshot is at seq %d, want %d", snapshots[0].Seq, snapshotCadence)
74 }
75 if len(snapshots[0].Bytes) == 0 {
76 t.Error("the snapshot carries no bytes")
77 }
78
79 // openDeck replays the tail and nothing before it.
80 snap, tail, err := a.replayPlan(ctx, row)
81 if err != nil {
82 t.Fatalf("replayPlan: %v", err)
83 }
84 if snap == nil || snap.Seq != snapshotCadence {
85 t.Fatalf("replayPlan chose %+v, want the snapshot at seq %d", snap, snapshotCadence)
86 }
87 if len(tail) != 1 || tail[0].Seq != row.Seq {
88 t.Fatalf("replayPlan replays %d updates, want the one at seq %d", len(tail), row.Seq)
89 }
90
91 // And it is the same deck either way.
92 want, err := h.JSON(ctx)
93 if err != nil {
94 t.Fatalf("read the live view: %v", err)
95 }
96 fromSnapshot := viewJSON(t, a, row)
97 if string(fromSnapshot) != string(want) {
98 t.Fatalf("opening from the snapshot gives a different deck:\n got %s\nwant %s", fromSnapshot, want)
99 }
100
101 // Deleting the snapshot loses nothing: the log alone still is the
102 // deck, which is what makes a snapshot safe to throw away.
103 if err := d.G.WithContext(ctx).Where("deck_id = ?", row.ID).Delete(&DeckSnapshot{}).Error; err != nil {
104 t.Fatalf("delete snapshots: %v", err)
105 }
106 if fromLog := viewJSON(t, a, row); string(fromLog) != string(want) {
107 t.Fatalf("opening from the plain log gives a different deck:\n got %s\nwant %s", fromLog, want)
108 }
109}
110
111// viewJSON opens d the way a request does and returns the core's view.
112func viewJSON(t *testing.T, a *app, d Deck) []byte {
113 t.Helper()
114 ctx := context.Background()
115 h, _, err := a.openDeck(ctx, d)
116 if err != nil {
117 t.Fatalf("openDeck: %v", err)
118 }
119 defer h.Close(ctx)
120 raw, err := h.JSON(ctx)
121 if err != nil {
122 t.Fatalf("read the view: %v", err)
123 }
124 return raw
125}
126