package config import ( "log/slog" "testing" "time" ) func TestLoadDefaultsAndValidation(t *testing.T) { t.Setenv("CONTROL_SESSION_SECRET", "0123456789abcdef") t.Setenv("CONTROL_ADMIN_PASSWORD", "hunter2") t.Setenv("CONTROL_ADDR", "") t.Setenv("CONTROL_DB_PATH", "") // The lab broker has no TLS listener yet, so the pre-TLS escape hatch is // the current deployment default (see CONTROL_MQTT_INSECURE). t.Setenv("CONTROL_MQTT_INSECURE", "true") cfg, err := Load() if err != nil { t.Fatalf("Load: %v", err) } if cfg.Addr != ":8791" { t.Errorf("Addr = %q, want :8791", cfg.Addr) } if cfg.DBPath != "/data/control.db" { t.Errorf("DBPath = %q, want /data/control.db", cfg.DBPath) } if cfg.AdminUsername != "sam" { t.Errorf("AdminUsername = %q, want sam", cfg.AdminUsername) } if cfg.SessionTTL != 12*time.Hour { t.Errorf("SessionTTL = %v, want 12h", cfg.SessionTTL) } if cfg.LogLevel != slog.LevelInfo { t.Errorf("LogLevel = %v, want info", cfg.LogLevel) } if !cfg.CookieSecure { t.Error("CookieSecure = false, want true by default") } } func TestLoadRequiresSecretAndPassword(t *testing.T) { t.Setenv("CONTROL_SESSION_SECRET", "") t.Setenv("CONTROL_ADMIN_PASSWORD", "hunter2") if _, err := Load(); err == nil { t.Error("expected error when session secret is missing") } t.Setenv("CONTROL_SESSION_SECRET", "0123456789abcdef") t.Setenv("CONTROL_ADMIN_PASSWORD", "") t.Setenv("CONTROL_ADMIN_PASSWORD_HASH", "") if _, err := Load(); err == nil { t.Error("expected error when no admin password is set") } } func TestMQTTDefaultsAndGuards(t *testing.T) { t.Setenv("CONTROL_SESSION_SECRET", "0123456789abcdef") t.Setenv("CONTROL_ADMIN_PASSWORD", "hunter2") // Enabled by default, pointed at the shared Mosquitto on `.13`. t.Setenv("CONTROL_MQTT_INSECURE", "true") cfg, err := Load() if err != nil { t.Fatalf("Load: %v", err) } if !cfg.MQTTEnabled { t.Error("MQTTEnabled = false, want true by default") } if cfg.MQTTBroker != DefaultMQTTBroker { t.Errorf("MQTTBroker = %q, want %q", cfg.MQTTBroker, DefaultMQTTBroker) } if cfg.MQTTTLS() { t.Error("MQTTTLS = true for the default plaintext broker, want false") } // Without the escape hatch, a plaintext credential-less broker is refused // (AGENTS.md ยง10 rule 6). t.Setenv("CONTROL_MQTT_INSECURE", "false") if _, err := Load(); err == nil { t.Error("expected an error for a plaintext broker without CONTROL_MQTT_INSECURE") } // TLS plus credentials is the supported shape. t.Setenv("CONTROL_MQTT_BROKER", "tls://mosquitto.lab.audasmedia.com.au:8883") t.Setenv("CONTROL_MQTT_USERNAME", "control-service") t.Setenv("CONTROL_MQTT_PASSWORD", "secret") cfg, err = Load() if err != nil { t.Fatalf("Load with TLS: %v", err) } if !cfg.MQTTTLS() { t.Error("MQTTTLS = false for a tls:// broker, want true") } // MQTT can be switched off entirely. t.Setenv("CONTROL_MQTT_ENABLED", "false") t.Setenv("CONTROL_MQTT_BROKER", "") t.Setenv("CONTROL_MQTT_USERNAME", "") t.Setenv("CONTROL_MQTT_PASSWORD", "") cfg, err = Load() if err != nil { t.Fatalf("Load with MQTT disabled: %v", err) } if cfg.MQTTEnabled { t.Error("MQTTEnabled = true, want false when CONTROL_MQTT_ENABLED=false") } }