| 1 | package slides |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "testing" |
| 6 | "time" |
| 7 | ) |
| 8 | |
| 9 | func TestUnreadableSnapshotFallsBackToAuthoritativeLog(t *testing.T) { |
| 10 | db := testDB(t) |
| 11 | a, _, err := newApp(db, Config{Env: EnvDevelopment, Logger: quietLogger()}) |
| 12 | if err != nil { |
| 13 | t.Fatal(err) |
| 14 | } |
| 15 | t.Cleanup(func() { a.Close() }) |
| 16 | ctx := context.Background() |
| 17 | owner, _, err := ensureUser(ctx, db.G, "snapshot-owner@example.test") |
| 18 | if err != nil { |
| 19 | t.Fatal(err) |
| 20 | } |
| 21 | row, err := a.createDeckRow(ctx, owner, "Preserved deck", defaultTheme) |
| 22 | if err != nil { |
| 23 | t.Fatal(err) |
| 24 | } |
| 25 | opening, err := openingOps("Preserved deck", defaultTheme) |
| 26 | if err != nil { |
| 27 | t.Fatal(err) |
| 28 | } |
| 29 | if err := a.editDeck(ctx, &row, actorFor(owner), opening, nil); err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | batch, err := opsJSON([]op{{"op": "set_meta", "title": "Newest log title"}}) |
| 33 | if err != nil { |
| 34 | t.Fatal(err) |
| 35 | } |
| 36 | if err := a.editDeck(ctx, &row, actorFor(owner), batch, nil); err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | want := viewJSON(t, a, row) |
| 40 | snapshot := DeckSnapshot{DeckID: row.ID, Seq: 1, Bytes: []byte{0xff}, CreatedAt: time.Now().UTC()} |
| 41 | if err := db.G.Create(&snapshot).Error; err != nil { |
| 42 | t.Fatal(err) |
| 43 | } |
| 44 | got := viewJSON(t, a, row) |
| 45 | if string(got) != string(want) { |
| 46 | t.Fatal("fallback changed the authoritative deck") |
| 47 | } |
| 48 | var count int64 |
| 49 | if err := db.G.Model(&DeckUpdate{}).Where("deck_id = ?", row.ID).Count(&count).Error; err != nil { |
| 50 | t.Fatal(err) |
| 51 | } |
| 52 | if count != row.Seq { |
| 53 | t.Fatal("fallback changed the update log") |
| 54 | } |
| 55 | var persisted DeckSnapshot |
| 56 | if err := db.G.First(&persisted, snapshot.ID).Error; err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | if string(persisted.Bytes) != string(snapshot.Bytes) { |
| 60 | t.Fatal("read fallback changed the stored snapshot") |
| 61 | } |
| 62 | |
| 63 | if err := db.G.Model(&DeckUpdate{}).Where("deck_id = ? AND seq = ?", row.ID, row.Seq).Update("bytes", []byte{0xff}).Error; err != nil { |
| 64 | t.Fatal(err) |
| 65 | } |
| 66 | h, view, err := a.openDeck(ctx, row) |
| 67 | if h != nil { |
| 68 | h.Close(ctx) |
| 69 | } |
| 70 | if err == nil || h != nil || view != nil { |
| 71 | t.Fatal("fallback concealed corruption in the authoritative log") |
| 72 | } |
| 73 | } |
| 74 | |