// The store paths the happy-path engine tests cannot reach: every failure // return in Open (a broken bit of the directory layout) and the stale // checkpoint recovery, which rebuilds the indexes from the WAL. package store import ( "os" "testing" "git.smesh.lol/smesh/pkg/nostr/filter" "git.smesh.lol/smesh/pkg/store/checkpoint" ) // engIndexNames is the index file set Open creates, in Open's order. var engIndexNames = []string{ "eid", "fpc", "sei", "ca", "exp", "kc", "pc", "kpc", "tc", "tkc", "tpc", "tkp", "wrd", "pks", "spk", "epg", "peg", "eeg", "gee", "ppg", "gpp", } func TestOpenFailsOnABrokenIndexFile(t *testing.T) { for i := 0; i < len(engIndexNames); i++ { dir, ok := engTmp(t) if !ok { return } // A directory where the index file belongs makes sorted.Open fail, so // Open must report that index's error and not hand back an engine. if err := os.MkdirAll(dir|"/idx/"|engIndexNames[i]|".dat", 0755); err != nil { t.Fatal(err) } if g, oerr := Open(dir); oerr == nil { g.Close() t.Fatalf("Open must fail when idx/%s.dat is a directory", engIndexNames[i]) } os.RemoveAll(dir) } } func TestOpenFailsOnABrokenWAL(t *testing.T) { // A regular file where the wal directory belongs: wal.Open's MkdirAll and // then ReadDir/OpenFile must fail. dir, ok := engTmp(t) if !ok { return } if err := os.WriteFile(dir|"/wal", []byte("not a directory"), 0644); err != nil { t.Fatal(err) } if g, oerr := Open(dir); oerr == nil { g.Close() t.Fatal("Open must fail when the wal path is a file") } os.RemoveAll(dir) // A segment that cannot be opened is reported too. dir2, ok2 := engTmp(t) if !ok2 { return } if err := os.MkdirAll(dir2|"/wal/seg-0000000000000000.dat", 0755); err != nil { t.Fatal(err) } if g, oerr := Open(dir2); oerr == nil { g.Close() t.Fatal("Open must fail when a wal segment cannot be opened") } os.RemoveAll(dir2) } func TestOpenFailsOnABrokenCheckpoint(t *testing.T) { dir, ok := engTmp(t) if !ok { return } if err := os.MkdirAll(dir|"/checkpoint.dat", 0755); err != nil { t.Fatal(err) } if g, oerr := Open(dir); oerr == nil { g.Close() t.Fatal("Open must fail when checkpoint.dat cannot be read") } os.RemoveAll(dir) } func TestOpenFailsWhenTheDirectoryCannotBeCreated(t *testing.T) { dir, ok := engTmp(t) if !ok { return } // A path through a regular file cannot become a directory. file := dir | "/file" if err := os.WriteFile(file, []byte("x"), 0644); err != nil { t.Fatal(err) } if g, oerr := Open(file | "/sub"); oerr == nil { g.Close() t.Fatal("Open must fail when the store directory cannot be created") } os.RemoveAll(dir) } func TestOpenRecoversFromAStaleCheckpoint(t *testing.T) { g, dir, ok := engOpenTmp(t) if !ok { return } ev := engEvent(0x31, 0x32, 1, 1700000000, engTag("t", "stale"), "stale checkpoint") if !engSaveAll(t, g, []*event.E{ev}) { g.Close() os.RemoveAll(dir) return } if cerr := g.Close(); cerr != nil { t.Fatal(cerr) } // A checkpoint offset beyond the segment's size is stale: Open must clear // the indexes and rebuild them from the WAL instead of failing. ck, cok := checkpoint.Open(dir | "/checkpoint.dat") if cok != nil { t.Fatal(cok) } if serr := ck.Set(1 << 20); serr != nil { t.Fatal(serr) } g2, ok2 := engOpen(t, dir) if !ok2 { return } defer g2.Close() res, qok := engQuery(t, g2, filter.New()) if !qok { return } if len(res) != 1 { t.Fatalf("the rebuilt store returned %d events, want the one in the WAL", int32(len(res))) } if string(res[0].Content) != "stale checkpoint" { t.Fatalf("rebuilt event content = %s", res[0].Content) } os.RemoveAll(dir) }