oficina / sheets Public

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

Plain git — no account needed to clone.

Download

Download this file

1package store_test
2
3import (
4 "testing"
5
6 "amadan.net/oficina/sheets/internal/sheets/store"
7)
8
9// TestEnsureRowRetriesOnConcurrentCreate simulates the race a second
10// process can win: a Row lands at (tabID, pos) through a path this
11// handle's own rowByPos never observes (a raw db.Create standing in for
12// another process's Handle), so when THIS handle's EnsureRow reaches the
13// same position it must lose the Create to the uniqueIndex on
14// (TabID, Position) — models.go — and recover by reading the row back,
15// not by erroring or creating a second, colliding Row.
16func TestEnsureRowRetriesOnConcurrentCreate(t *testing.T) {
17 db, sealer, tabID := emptyTab(t)
18 h, err := store.Open(db, sealer, tabID)
19 if err != nil {
20 t.Fatalf("Open: %v", err)
21 }
22 defer h.Release()
23
24 const pos = 3
25 winner := store.Row{TabID: tabID, Position: pos}
26 if err := db.Create(&winner).Error; err != nil {
27 t.Fatalf("seed concurrent row: %v", err)
28 }
29
30 got, err := h.EnsureRow(pos)
31 if err != nil {
32 t.Fatalf("EnsureRow after a concurrent create: %v", err)
33 }
34 if got != winner.ID {
35 t.Fatalf("EnsureRow(%d) = %d, want the concurrently-created row's id %d", pos, got, winner.ID)
36 }
37
38 var count int64
39 if err := db.Model(&store.Row{}).Where("tab_id = ? AND position = ?", tabID, pos).Count(&count).Error; err != nil {
40 t.Fatalf("count rows at position: %v", err)
41 }
42 if count != 1 {
43 t.Fatalf("%d rows claim position %d, want exactly 1 — the retry created a duplicate", count, pos)
44 }
45
46 // A subsequent call must still be idempotent, now served from the
47 // handle's own (now-populated) rowByPos rather than hitting the db.
48 got2, err := h.EnsureRow(pos)
49 if err != nil || got2 != winner.ID {
50 t.Fatalf("EnsureRow(%d) again = %d, %v, want %d, nil", pos, got2, err, winner.ID)
51 }
52}
53
54// EnsureCol's counterpart to the above.
55func TestEnsureColRetriesOnConcurrentCreate(t *testing.T) {
56 db, sealer, tabID := emptyTab(t)
57 h, err := store.Open(db, sealer, tabID)
58 if err != nil {
59 t.Fatalf("Open: %v", err)
60 }
61 defer h.Release()
62
63 const pos = 3
64 winner := store.Col{TabID: tabID, Position: pos}
65 if err := db.Create(&winner).Error; err != nil {
66 t.Fatalf("seed concurrent col: %v", err)
67 }
68
69 got, err := h.EnsureCol(pos)
70 if err != nil {
71 t.Fatalf("EnsureCol after a concurrent create: %v", err)
72 }
73 if got != winner.ID {
74 t.Fatalf("EnsureCol(%d) = %d, want the concurrently-created col's id %d", pos, got, winner.ID)
75 }
76
77 var count int64
78 if err := db.Model(&store.Col{}).Where("tab_id = ? AND position = ?", tabID, pos).Count(&count).Error; err != nil {
79 t.Fatalf("count cols at position: %v", err)
80 }
81 if count != 1 {
82 t.Fatalf("%d cols claim position %d, want exactly 1 — the retry created a duplicate", count, pos)
83 }
84}
85
86// TestRowUniqueIndexRejectsDuplicatePosition pins the constraint itself,
87// independent of EnsureRow/EnsureCol's recovery: two Row records cannot
88// claim the same (TabID, Position) at the database level at all, which is
89// the guarantee the retry-by-read logic above depends on existing.
90func TestRowUniqueIndexRejectsDuplicatePosition(t *testing.T) {
91 db, _, tabID := emptyTab(t)
92 if err := db.Create(&store.Row{TabID: tabID, Position: 0}).Error; err != nil {
93 t.Fatalf("first create: %v", err)
94 }
95 if err := db.Create(&store.Row{TabID: tabID, Position: 0}).Error; err == nil {
96 t.Fatal("second Row at the same (TabID, Position) succeeded, want a uniqueIndex violation")
97 }
98}
99