Fix two preflight defects a live production rehearsal found

Ran the rehearsal read-only against a live production instance. It
completed, and two preflight checks were wrong in ways a test instance
could not have shown.

1. Store-backend detection missed the config entirely. A config generated
   by `stalwart --init` declares `type = "rocksdb"` inside a
   `[store.rocksdb]` section, which is what every test fixture here used.
   The production config has no section headers at all and declares
   `store.rocksdb.type = "rocksdb"` flat. Detection only matched a bare
   `type` key, so it reported "no known store backend type found in config"
   and left topology.store_backend empty.

   That mattered more than a warning suggests: backup.Run treated an
   unrecognized backend as a *skip*, so a real run would have continued
   with no filesystem or database backup at all - the single artifact that
   phase exists to produce, quietly absent. Flat dotted keys are now
   detected, and an unrecognized backend is a hard failure rather than a
   skip.

2. The cluster warning didn't say where it matched. It fires on any
   occurrence of "cluster" anywhere in the config, which is the correct
   bias - a missed cluster corrupts a shared store - but on the production
   config the only match was inside the value of an unrelated setting,
   leaving a whole config to search to establish that. It now names the
   location and distinguishes a match in the setting name from one in its
   value.

Both verified against the real config: store-backend now reports
"rocksdb (store.rocksdb)", and the cluster warning names the setting,
making a false positive dismissible at a glance.

No production data is in this commit: the fixtures use example.com and the
flat-key shape only. Coverage numbers from that run matched the earlier
scrubbed-corpus measurement exactly.
This commit is contained in:
2026-08-23 21:58:53 -07:00
parent 1684c88877
commit a69f0bbff1
7 changed files with 199 additions and 13 deletions
+50
View File
@@ -87,3 +87,53 @@ func TestDetectStoreBackendsNoMatch(t *testing.T) {
t.Errorf("got %d matches, want 0: %+v", len(matches), matches)
}
}
// A real production config declares its backend with flat dotted keys and
// has no section headers at all. Checking only for a bare `type` key found
// nothing there, and an undetected backend makes the backup phase skip the
// filesystem snapshot - the artifact it exists to produce.
func TestDetectStoreBackendsHandlesFlatDottedKeys(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "config.toml")
// Shape taken from a real 0.15.5 instance: no [sections] anywhere.
body := `storage.blob = "rocksdb"
storage.data = "rocksdb"
storage.directory = "internal"
store.rocksdb.compression = "lz4"
store.rocksdb.path = "/opt/stalwart/data"
store.rocksdb.type = "rocksdb"
directory.internal.type = "internal"
`
if err := os.WriteFile(path, []byte(body), 0o640); err != nil {
t.Fatal(err)
}
matches, err := DetectStoreBackends(path)
if err != nil {
t.Fatal(err)
}
if len(matches) != 1 {
t.Fatalf("found %d backend(s), want 1: %+v", len(matches), matches)
}
if matches[0].Backend != "rocksdb" {
t.Errorf("backend = %q, want rocksdb", matches[0].Backend)
}
if matches[0].Path != "store.rocksdb" {
t.Errorf("path = %q, want store.rocksdb", matches[0].Path)
}
}
// The sectioned form must keep working; both spellings are real.
func TestDetectStoreBackendsStillHandlesSections(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "config.toml")
if err := os.WriteFile(path, []byte("[store.rocksdb]\ntype = \"rocksdb\"\npath = \"/var/lib/stalwart\"\n"), 0o640); err != nil {
t.Fatal(err)
}
matches, err := DetectStoreBackends(path)
if err != nil {
t.Fatal(err)
}
if len(matches) != 1 || matches[0].Backend != "rocksdb" || matches[0].Path != "store.rocksdb" {
t.Errorf("matches = %+v, want one rocksdb at store.rocksdb", matches)
}
}