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.
64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
// SPDX-FileCopyrightText: 2026 LINUXexpert-org
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package preflight
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestLooksClustered(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
content string
|
|
want bool
|
|
}{
|
|
{"no-cluster", "[server]\nhostname = \"mail.example.com\"\n", false},
|
|
{"has-cluster-section", "[cluster]\nnode-id = 1\npeers = [\"10.0.0.2\"]\n", true},
|
|
{"cluster-mentioned-in-key", "[server]\ncluster-coordinator = \"redis://localhost\"\n", true},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.toml")
|
|
if err := os.WriteFile(path, []byte(tc.content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := LooksClustered(path)
|
|
if err != nil {
|
|
t.Fatalf("LooksClustered: %v", err)
|
|
}
|
|
if got != tc.want {
|
|
t.Errorf("LooksClustered(%s) = %v, want %v", tc.name, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Against a real instance the only match was inside the value of an
|
|
// unrelated setting. A bare "config mentions clustering" left a whole
|
|
// config to search; naming the location makes it dismissible at a glance.
|
|
func TestClusterMentionsSaysWhereItMatched(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.toml")
|
|
body := "server.hostname = \"mail.example.com\"\nconfig.local-keys.08 = \"cluster.*\"\n"
|
|
if err := os.WriteFile(path, []byte(body), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mentions, err := ClusterMentions(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(mentions) != 1 {
|
|
t.Fatalf("mentions = %v, want one", mentions)
|
|
}
|
|
if !strings.Contains(mentions[0], "config.local-keys.08") {
|
|
t.Errorf("mention = %q, should name the setting", mentions[0])
|
|
}
|
|
if !strings.Contains(mentions[0], "in its value") {
|
|
t.Errorf("mention = %q, should distinguish a value match from a real cluster setting", mentions[0])
|
|
}
|
|
}
|