35c6ff5a71
Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
111 lines
4.0 KiB
Go
111 lines
4.0 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestDeployConfigLoads parses the file the box actually runs on.
|
|
//
|
|
// Every other test in this package builds its own JSON, so a key renamed in one
|
|
// place and not the other would go unnoticed until the daemon refused to start.
|
|
// This one reads deploy/mavend.json through the same Load the daemon calls, so
|
|
// a config change and a code change have to agree here or the suite is red.
|
|
//
|
|
// The ${VAR} expansions come from a gitignored deploy/telegram.env that is not
|
|
// present in CI. Live blocks reject empty secrets; this test supplies inert
|
|
// Telegram values and verifies that every credential-less block is explicitly
|
|
// disabled.
|
|
func TestDeployConfigLoads(t *testing.T) {
|
|
path := filepath.Join("..", "..", "deploy", "mavend.json")
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Skipf("no deploy config at %s: %v", path, err)
|
|
}
|
|
// Live blocks fail on expanded-empty credentials. CI provides inert values
|
|
// so this test exercises the committed shape; explicitly disabled blocks
|
|
// (ntfy, workstation and Home Assistant) require none.
|
|
t.Setenv("TELEGRAM_BOT_TOKEN", "test-token")
|
|
t.Setenv("TELEGRAM_CHAT_ID", "-1001234567890")
|
|
t.Setenv("MAVEN_STT_TOKEN", "test-token")
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load(%s): %v", path, err)
|
|
}
|
|
|
|
// Spot-check the settings whose absence would be a silent behaviour change
|
|
// rather than a startup error.
|
|
if cfg.Phraser == nil {
|
|
t.Fatal("deploy config has no phraser block")
|
|
}
|
|
if cfg.Phraser.NGpuLayers == 0 {
|
|
t.Error("phraser.n_gpu_layers is 0 — llama-server would run CPU-only, " +
|
|
"because nothing in this package defaults that field")
|
|
}
|
|
if cfg.Voice == nil || !cfg.Voice.Enabled {
|
|
t.Fatal("deploy config does not enable voice")
|
|
}
|
|
if !cfg.Voice.UseLLMRouter() {
|
|
t.Error("deploy config turned the LLM router off")
|
|
}
|
|
if cfg.Voice.RouterThreshold <= 0 {
|
|
t.Error("router threshold did not get its default")
|
|
}
|
|
|
|
// The second reach (V-649). The token is a ${VAR} that CI cannot resolve, so
|
|
// the committed deployment makes the dark state explicit. Removing disabled
|
|
// without provisioning a credential makes runtime wiring fail startup; it
|
|
// can never silently publish anonymously.
|
|
if cfg.Ntfy == nil {
|
|
t.Fatal("deploy config has no ntfy block — sev3-away and away reminders " +
|
|
"would have nowhere to land, and would vanish silently rather than fail")
|
|
}
|
|
if cfg.Ntfy.BaseURL == "" || cfg.Ntfy.Topic == "" {
|
|
t.Errorf("ntfy block is incomplete: base_url=%q topic=%q", cfg.Ntfy.BaseURL, cfg.Ntfy.Topic)
|
|
}
|
|
if !cfg.Ntfy.Disabled {
|
|
t.Fatal("deploy ntfy reach has no checked-in credential and must remain explicitly disabled")
|
|
}
|
|
if cfg.Workstation == nil || !cfg.Workstation.ModelDisabled || cfg.Workstation.Stt == nil {
|
|
t.Fatalf("deploy must disable only its uncredentialed model arm and retain authenticated STT: %+v", cfg.Workstation)
|
|
}
|
|
}
|
|
|
|
func TestCanonicalDeployEnvExampleNamesEverySecret(t *testing.T) {
|
|
path := filepath.Join("..", "..", "deploy", "telegram.env.example")
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read %s: %v", path, err)
|
|
}
|
|
example := string(b)
|
|
references := map[string]bool{
|
|
// These two live outside ${...}: DBKeyEnv names an environment variable
|
|
// as JSON data, while the separately deployed Python child reads its own
|
|
// environment directly.
|
|
"CW2_TOKEN": true,
|
|
"MAVEN_DB_KEY": true,
|
|
}
|
|
for _, source := range []string{
|
|
filepath.Join("..", "..", "deploy", "mavend.json"),
|
|
filepath.Join("..", "..", "docker-compose.yml"),
|
|
} {
|
|
raw, err := os.ReadFile(source)
|
|
if err != nil {
|
|
t.Fatalf("read deploy source %s: %v", source, err)
|
|
}
|
|
_ = os.Expand(string(raw), func(expr string) string {
|
|
// Compose supports ${NAME:-default}; os.Expand deliberately hands the
|
|
// full braced expression to this callback.
|
|
name, _, _ := strings.Cut(expr, ":-")
|
|
references[name] = true
|
|
return ""
|
|
})
|
|
}
|
|
for name := range references {
|
|
if !strings.Contains(example, name+"=") {
|
|
t.Errorf("canonical deploy env example omits %s", name)
|
|
}
|
|
}
|
|
}
|