Files
Maven/internal/config/deployconfig_test.go
T
claude 06576b406c Pass the ambient boolean as one flag argument
Go flag parsing stops at the separate boolean value before ambient-token. Use -ambient-enabled=value and pin the deployed argv contract discovered during live V-691 verification. Owner explicitly requested direct commits to master.
2026-08-13 03:07:52 +04:00

131 lines
4.9 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)
}
}
}
// Go's flag package treats a bare boolean flag as true and does not consume a
// following "true"/"false" argument. That following value becomes the first
// positional argument and stops parsing, so every flag after it silently keeps
// its default. Keep the Compose boolean in -name=value form (V-691 live QA).
func TestComposeAmbientBooleanDoesNotStopFlagParsing(t *testing.T) {
path := filepath.Join("..", "..", "docker-compose.yml")
b, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
compose := string(b)
const joined = `"-ambient-enabled=${MAVEN_AMBIENT_ENABLED:-false}"`
if !strings.Contains(compose, joined) {
t.Fatalf("mavweb ambient boolean must be one argv element %s", joined)
}
if strings.Contains(compose, `"-ambient-enabled",`) {
t.Fatal("bare -ambient-enabled leaves its value positional and prevents -ambient-token from being parsed")
}
}