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
3// This file closes a gap Task 2 explicitly left open. Task 2 built the
4// plaintext gate and proved it can fail, but only against a plain file
5// standing in for a database, with a synthetic -wal sidecar it seeded
6// itself. There was no real store and no real SQLite driver yet.
7//
8// Now there is one. These tests prove, against the actual rastrillo/db
9// driver in actual WAL mode:
10//
11// 1. TestUncheckpointedWriteLandsInRealWALFile: a write that is never
12// checkpointed really does put bytes in <db>-wal on disk (not just in
13// an in-process buffer), and sealtest.AssertNoPlaintext really does
14// find a plaintext canary there.
15// 2. TestSealedWriteIsUnflushedBeforeExplicitCheckpoint: nothing between
16// an insert and the gate implicitly checkpoints or vacuums the file, so
17// TestInputEventIsSealedOnDisk's own checkpoint() call is doing real
18// work — the gate in that test is inspecting a file settled by an
19// explicit action, not one that happened to already be settled for an
20// unrelated reason.
21
22import (
23 "bytes"
24 "os"
25 "os/exec"
26 "path/filepath"
27 "strings"
28 "testing"
29 "time"
30
31 "amadan.net/oficina/sheets/internal/sheets/seal"
32 "amadan.net/oficina/sheets/internal/sheets/sealtest"
33 "amadan.net/oficina/sheets/internal/sheets/store"
34)
35
36// TestUncheckpointedWriteLandsInRealWALFile writes a plaintext canary
37// directly through the real driver — standing in for a sealing bug, the
38// same way step 5 of the task brief breaks AppendInput — in WAL mode, and
39// deliberately never checkpoints. It then confirms two things in order:
40//
41// - mechanically, without going through sealtest at all, that the bytes
42// are really sitting in <db>-wal on disk and NOT yet in the main file;
43// if this failed, the whole proof would be worthless because it would
44// mean the write never left memory, or WAL mode was not actually on;
45// - that sealtest.AssertNoPlaintext, run against exactly this
46// uncheckpointed file, reports the canary living in the -wal file.
47//
48// The second half runs sealtest.AssertNoPlaintext in a subprocess: that
49// function calls t.Fatal on a real leak, and here a leak is exactly what
50// we expect, so the failure has to be captured rather than allowed to fail
51// this test's own build.
52func TestUncheckpointedWriteLandsInRealWALFile(t *testing.T) {
53 const canary = "CANARY-CELL-VALUE-8f3a"
54
55 dir := t.TempDir()
56 dbPath := filepath.Join(dir, "test.db")
57 db := openTestDB(t, dbPath)
58
59 var jm string
60 if err := db.Raw("PRAGMA journal_mode").Scan(&jm).Error; err != nil {
61 t.Fatal(err)
62 }
63 if jm != "wal" {
64 t.Fatalf("journal_mode = %q, want wal (rastrillo/db.Open's DSN should have set this)", jm)
65 }
66
67 // A raw insert of plaintext bypassing seal.Sealer entirely — this is
68 // what a sealing bug looks like on the wire, not what AppendInput does
69 // today.
70 if err := db.Exec(
71 `INSERT INTO events (kind, tab_id, actor_id, payload_sealed, trigger_id, derived_count, created_at)
72 VALUES (?, ?, ?, ?, ?, ?, ?)`,
73 store.KindInput, uint64(1), uint64(7), []byte(canary), uint64(0), 0, time.Now(),
74 ).Error; err != nil {
75 t.Fatalf("raw insert: %v", err)
76 }
77
78 walBytes, err := os.ReadFile(dbPath + "-wal")
79 if err != nil {
80 t.Fatalf("read WAL file: %v (a missing WAL file here would mean the write never reached disk before a checkpoint, making this proof meaningless)", err)
81 }
82 if len(walBytes) == 0 {
83 t.Fatal("WAL file is empty; the write did not land there")
84 }
85 if !bytes.Contains(walBytes, []byte(canary)) {
86 t.Fatal("canary not found in the uncheckpointed WAL file bytes; cannot confirm the write actually reached <db>-wal on disk")
87 }
88 mainBytes, err := os.ReadFile(dbPath)
89 if err != nil {
90 t.Fatalf("read main db file: %v", err)
91 }
92 if bytes.Contains(mainBytes, []byte(canary)) {
93 t.Fatal("canary already present in the main db file; this write was not actually sitting only in the WAL, which defeats the point of this test")
94 }
95
96 // Now ask the real gate. Run in a subprocess because AssertNoPlaintext
97 // is expected to Fatal here, and that is the assertion, not a bug.
98 if os.Getenv("SHEETS_WAL_GATE_SUBPROCESS") == "1" {
99 sealtest.AssertNoPlaintext(t, dbPath)
100 return
101 }
102 cmd := exec.Command(os.Args[0],
103 "-test.run=^TestUncheckpointedWriteLandsInRealWALFile$", "-test.v")
104 cmd.Env = append(os.Environ(), "SHEETS_WAL_GATE_SUBPROCESS=1")
105 // The subprocess re-executes this whole test function, including the
106 // setup above, into a NEW temp dir (t.TempDir() differs per process),
107 // so it performs its own insert rather than reusing dbPath. That is
108 // fine: the property under test — an uncheckpointed real WAL write is
109 // caught — does not depend on reusing the parent's files.
110 out, runErr := cmd.CombinedOutput()
111 if runErr == nil {
112 t.Fatalf("expected the gate subprocess to fail against an uncheckpointed WAL write; it exited 0:\n%s", out)
113 }
114 if !strings.Contains(string(out), "test.db-wal") {
115 t.Fatalf("gate subprocess failed, but not evidently by finding the canary in the WAL file; output:\n%s", out)
116 }
117 if !strings.Contains(string(out), canary) {
118 t.Fatalf("gate subprocess failure did not name the canary; output:\n%s", out)
119 }
120}
121
122// TestSealedWriteIsUnflushedBeforeExplicitCheckpoint proves that nothing
123// between AppendInput and the gate implicitly checkpoints (or vacuums) the
124// database. If it did, TestInputEventIsSealedOnDisk's checkpoint() call
125// would be inspecting a file that was already settled for some unrelated
126// reason — a gate that would appear to work but would actually be agreeing
127// with itself, exactly what the brief warns against.
128func TestSealedWriteIsUnflushedBeforeExplicitCheckpoint(t *testing.T) {
129 dir := t.TempDir()
130 dbPath := filepath.Join(dir, "test.db")
131 db := openTestDB(t, dbPath)
132 s, err := seal.New(make32())
133 if err != nil {
134 t.Fatal(err)
135 }
136
137 if err := store.AppendInput(db, s, store.InputEvent{
138 TabID: 1, RowID: 1, ColID: 1, ActorID: 7,
139 NewRaw: "CANARY-CELL-VALUE-8f3a",
140 }); err != nil {
141 t.Fatal(err)
142 }
143
144 // Read back exactly the row just written, so the check below can prove
145 // THIS write's bytes are unflushed — not merely that the WAL file is
146 // non-empty, which a completely unrelated pending write could also
147 // satisfy and would say nothing about this insert specifically.
148 var written store.Event
149 if err := db.Where("kind = ?", store.KindInput).Order("id DESC").
150 First(&written).Error; err != nil {
151 t.Fatalf("read back the just-written event: %v", err)
152 }
153 if len(written.PayloadSealed) == 0 {
154 t.Fatal("just-written event has no sealed payload to trace into the WAL")
155 }
156
157 // Before any explicit checkpoint call: the WAL file must already exist,
158 // be non-empty, and contain THIS row's sealed payload bytes verbatim
159 // (the AES-GCM ciphertext is unique per call, courtesy of its random
160 // nonce, so this is not a coincidental match). If any of that is not
161 // so, the insert above was already folded into the main file by
162 // something other than an explicit checkpoint — a connection-close
163 // checkpoint, pool churn, migrate.Apply side effects, or journal_mode
164 // defaulting to something other than WAL — and the sealed-path test's
165 // checkpoint() call would be a no-op.
166 info, err := os.Stat(dbPath + "-wal")
167 if err != nil {
168 t.Fatalf("stat WAL before any explicit checkpoint: %v", err)
169 }
170 if info.Size() == 0 {
171 t.Fatal("WAL file is empty before the explicit checkpoint call; the write was already flushed by something implicit, which would make checkpoint() a no-op")
172 }
173 walBytes, err := os.ReadFile(dbPath + "-wal")
174 if err != nil {
175 t.Fatalf("read WAL before any explicit checkpoint: %v", err)
176 }
177 if !bytes.Contains(walBytes, written.PayloadSealed) {
178 t.Fatal("this write's sealed payload is not present in the uncheckpointed WAL bytes; a non-empty WAL file is not evidence that THIS write specifically is still unflushed")
179 }
180
181 var autoVacuum int
182 if err := db.Raw("PRAGMA auto_vacuum").Scan(&autoVacuum).Error; err != nil {
183 t.Fatal(err)
184 }
185 if autoVacuum != 0 {
186 t.Fatalf("auto_vacuum = %d, want 0 (off); an active auto-vacuum could rewrite pages behind the gate's back", autoVacuum)
187 }
188
189 // checkpoint() itself is exercised, and its precondition proven
190 // meaningful, by TestInputEventIsSealedOnDisk. This test's job stops
191 // at proving that precondition — that there is real, unflushed work
192 // for it to do.
193}
194