oficina / home Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package provisioner
4
5import (
6 family "amadan.net/rastrillo/rastrillo/crypto"
7 "bytes"
8 "context"
9 "errors"
10 "io"
11 "os"
12 "os/exec"
13 "path/filepath"
14 "testing"
15)
16
17func journalFixture(t *testing.T) *journal {
18 t.Helper()
19 f, k := floorFixture(t)
20 if e := f.initialize(k); e != nil {
21 t.Fatal(e)
22 }
23 d, e := openPrivateDirectory(persistentDirectory(t))
24 if e != nil {
25 t.Fatal(e)
26 }
27 t.Cleanup(func() { d.close() })
28 j, e := openJournal(d, f, k, true)
29 if e != nil {
30 t.Fatal(e)
31 }
32 t.Cleanup(func() { j.close() })
33 return j
34}
35func TestJournalCrashRecoveryAndExactReplay(t *testing.T) {
36 for _, boundary := range []string{"prepared", "floor", "finalized"} {
37 t.Run(boundary, func(t *testing.T) {
38 j := journalFixture(t)
39 synthetic := []byte("private-owner-481@example.invalid")
40 stop := errors.New("synthetic interruption")
41 j.boundary = func(stage string) error {
42 if stage == boundary {
43 return stop
44 }
45 return nil
46 }
47 if _, e := j.append(context.Background(), testToken(5), testToken(6), "directory.seed-team", "unseeded", synthetic); e != stop {
48 t.Fatal("boundary not reached", e)
49 }
50 j.close()
51 reopened, e := openJournal(j.dir, j.floor, j.key, false)
52 if e != nil {
53 t.Fatal("resume", e)
54 }
55 defer reopened.close()
56 first, e := reopened.append(context.Background(), testToken(5), testToken(6), "directory.seed-team", "unseeded", synthetic)
57 if e != nil {
58 t.Fatal(e)
59 }
60 repeated, e := reopened.append(context.Background(), testToken(5), testToken(6), "directory.seed-team", "unseeded", synthetic)
61 if e != nil || !bytes.Equal(first, repeated) {
62 t.Fatal("replay changed record", e)
63 }
64 if _, e = reopened.append(context.Background(), testToken(5), testToken(6), "directory.seed-team", "unseeded", []byte("changed")); e != ErrRefused {
65 t.Fatal("changed replay", e)
66 }
67 var n int
68 if e = reopened.db.QueryRow("SELECT count(*) FROM records WHERE state='finalized'").Scan(&n); e != nil || n != 1 {
69 t.Fatal("non-atomic journal", e)
70 }
71 for _, suffix := range []string{"", "-wal", "-shm"} {
72 data, e := os.ReadFile(filepath.Join(j.dir.file.Name(), "registry.db") + suffix)
73 if e != nil && !errors.Is(e, os.ErrNotExist) {
74 t.Fatal(e)
75 }
76 if bytes.Contains(data, synthetic) {
77 t.Fatal("plaintext journal payload")
78 }
79 }
80 })
81 }
82}
83func TestJournalSurvivingFloorRefusesOldBackup(t *testing.T) {
84 j := journalFixture(t)
85 if _, e := j.append(context.Background(), testToken(5), testToken(6), "directory.initialize", "absent", []byte("first private input")); e != nil {
86 t.Fatal(e)
87 }
88 if _, e := j.db.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); e != nil {
89 t.Fatal(e)
90 }
91 backup, e := os.ReadFile(filepath.Join(j.dir.file.Name(), "registry.db"))
92 if e != nil {
93 t.Fatal(e)
94 }
95 if _, e = j.append(context.Background(), testToken(5), testToken(7), "directory.seed-team", "empty", []byte("second private input")); e != nil {
96 t.Fatal(e)
97 }
98 j.close()
99 if e = os.WriteFile(filepath.Join(j.dir.file.Name(), "registry.db"), backup, 0600); e != nil {
100 t.Fatal(e)
101 }
102 if restored, e := openJournal(j.dir, j.floor, j.key, false); e != ErrRefused {
103 if restored != nil {
104 restored.close()
105 }
106 t.Fatal("stale backup accepted", e)
107 }
108}
109
110func TestJournalProcessDeathRecovery(t *testing.T) {
111 type childInput struct {
112 State, Floor, Boundary string
113 Identity floorIdentity
114 Key []byte
115 }
116 if os.Getenv("OFICINA_JOURNAL_CHILD") == "1" {
117 raw, e := io.ReadAll(io.LimitReader(os.Stdin, 16385))
118 if e != nil || len(raw) > 16384 {
119 t.Fatal("private child input")
120 }
121 var in childInput
122 if closedJSON(raw, &in) != nil {
123 t.Fatal("child shape")
124 }
125 key, e := family.UnmarshalKeypair(in.Key)
126 if e != nil {
127 t.Fatal("child key")
128 }
129 dir, e := openPrivateDirectory(in.State)
130 if e != nil {
131 t.Fatal(e)
132 }
133 defer dir.close()
134 floorDir, e := openPrivateDirectory(in.Floor)
135 if e != nil {
136 t.Fatal(e)
137 }
138 defer floorDir.close()
139 floor, e := newNativeFloor(floorDir, in.Identity)
140 if e != nil {
141 t.Fatal(e)
142 }
143 j, e := openJournal(dir, floor, key, false)
144 if e != nil {
145 t.Fatal(e)
146 }
147 defer j.close()
148 floor.markerBoundary = func() error {
149 if in.Boundary == "marker-created" {
150 os.Exit(72)
151 }
152 return nil
153 }
154 j.boundary = func(stage string) error {
155 if stage == in.Boundary {
156 os.Exit(72)
157 }
158 return nil
159 }
160 if _, e = j.append(context.Background(), testToken(5), testToken(6), "directory.seed-team", "empty", []byte("synthetic process-death payload")); e != nil {
161 t.Fatal(e)
162 }
163 t.Fatal("death boundary not reached")
164 }
165 for _, stage := range []string{"prepared", "marker-created", "floor", "finalized"} {
166 t.Run(stage, func(t *testing.T) {
167 j := journalFixture(t)
168 j.close()
169 key, e := family.MarshalKeypair(j.key)
170 if e != nil {
171 t.Fatal(e)
172 }
173 input, e := canonicalJSON(childInput{j.dir.file.Name(), j.floor.dir.file.Name(), stage, j.floor.identity, key})
174 if e != nil {
175 t.Fatal(e)
176 }
177 cmd := exec.Command(os.Args[0], "-test.run=^TestJournalProcessDeathRecovery$")
178 cmd.Env = append(os.Environ(), "OFICINA_JOURNAL_CHILD=1")
179 cmd.Stdin = bytes.NewReader(input)
180 output, e := cmd.CombinedOutput()
181 var exit *exec.ExitError
182 if !errors.As(e, &exit) || exit.ExitCode() != 72 {
183 t.Fatalf("child did not stop at durable boundary: %v %s", e, output)
184 }
185 reopened, e := openJournal(j.dir, j.floor, j.key, false)
186 if e != nil {
187 t.Fatal("process death resume", e)
188 }
189 defer reopened.close()
190 if _, e = reopened.append(context.Background(), testToken(5), testToken(6), "directory.seed-team", "empty", []byte("synthetic process-death payload")); e != nil {
191 t.Fatal(e)
192 }
193 point, _, e := j.floor.current()
194 if e != nil || point.Sequence != 1 {
195 t.Fatal("sequence reused", e)
196 }
197 if _, e = reopened.append(context.Background(), testToken(5), testToken(7), "directory.seed-team", "empty", []byte("next operation")); e != nil {
198 t.Fatal("next operation did not self-heal marker", e)
199 }
200 point, _, e = j.floor.current()
201 if e != nil || point.Sequence != 2 {
202 t.Fatal("next sequence", e)
203 }
204 })
205 }
206}
207
208func TestJournalPublicMetadataRejectsPrivateOrArbitraryText(t *testing.T) {
209 j := journalFixture(t)
210 for _, pair := range [][2]string{{"owner@example.invalid", "empty"}, {"directory.seed-team", "owner@example.invalid"}, {"arbitrary-sql", "empty"}} {
211 if _, e := j.append(context.Background(), testToken(5), testToken(6), pair[0], pair[1], []byte("sealed")); e != ErrRefused {
212 t.Fatal("arbitrary public journal metadata", e)
213 }
214 }
215 var n int
216 if e := j.db.QueryRow("SELECT count(*) FROM records").Scan(&n); e != nil || n != 0 {
217 t.Fatal("rejected metadata persisted", e)
218 }
219}
220