d52f60c54e
- Add CalendarEvents method to recordingAPI in auth_test.go - Add CalendarEvents method to fakeCore in handlers_test.go Co-Authored-By: opencode <opencode@anthropic.com>
191 lines
5.6 KiB
Go
191 lines
5.6 KiB
Go
package config
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func writeConfig(t *testing.T, body string) string {
|
|
t.Helper()
|
|
p := filepath.Join(t.TempDir(), "mavend.json")
|
|
if err := writeFile(t, p, body); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func TestLoadDefaults(t *testing.T) {
|
|
p := writeConfig(t, `{}`)
|
|
c, err := Load(p)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if time.Duration(c.TickInterval) != DefaultTickInterval {
|
|
t.Errorf("TickInterval default = %v, want %v", c.TickInterval, DefaultTickInterval)
|
|
}
|
|
if time.Duration(c.RepeatInterval) != DefaultRepeatInterval {
|
|
t.Errorf("RepeatInterval default = %v, want %v", c.RepeatInterval, DefaultRepeatInterval)
|
|
}
|
|
if c.DBPath == "" {
|
|
t.Error("DBPath default not applied")
|
|
}
|
|
if c.SocketPath == "" {
|
|
t.Error("SocketPath default not applied")
|
|
}
|
|
}
|
|
|
|
func TestLoadDurationsParse(t *testing.T) {
|
|
p := writeConfig(t, `{"tick_interval":"90s","repeat_interval":"10m"}`)
|
|
c, err := Load(p)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if time.Duration(c.TickInterval) != 90*time.Second {
|
|
t.Errorf("TickInterval = %v, want 90s", c.TickInterval)
|
|
}
|
|
if time.Duration(c.RepeatInterval) != 10*time.Minute {
|
|
t.Errorf("RepeatInterval = %v, want 10m", c.RepeatInterval)
|
|
}
|
|
}
|
|
|
|
func TestLoadBadDurationRejected(t *testing.T) {
|
|
p := writeConfig(t, `{"tick_interval":"not-a-duration"}`)
|
|
if _, err := Load(p); err == nil {
|
|
t.Fatal("Load succeeded for a bad duration; want error")
|
|
}
|
|
}
|
|
|
|
func TestLoadMissingFile(t *testing.T) {
|
|
p := filepath.Join(t.TempDir(), "nonexistent.json")
|
|
if _, err := Load(p); err == nil {
|
|
t.Fatal("Load succeeded for a missing file; want error")
|
|
}
|
|
}
|
|
|
|
func TestVoiceEnabledRequiresBind(t *testing.T) {
|
|
// enabled=true without bind is refused — the voice surface can't
|
|
// default a bind (127.0.0.1 too relaxed for production, a wg addr is
|
|
// the user's). surfacing the gap explicitly beats an idle listener.
|
|
p := writeConfig(t, `{"voice":{"enabled":true}}`)
|
|
if _, err := Load(p); err == nil {
|
|
t.Fatal("Load succeeded for voice.enabled=true with no bind; want error")
|
|
}
|
|
}
|
|
|
|
func TestVoiceEnabledWithBindOK(t *testing.T) {
|
|
// voice surface fully configured — accepted (the daemon wires Stub tts +
|
|
// voicesink; no models on disk required).
|
|
p := writeConfig(t, `{"voice":{"enabled":true,"bind":"127.0.0.1:9100"}}`)
|
|
if _, err := Load(p); err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEmbedderAllPathsSetOK(t *testing.T) {
|
|
p := writeConfig(t, `{
|
|
"voice": {
|
|
"enabled": true, "bind": "127.0.0.1:9100",
|
|
"embedder": {
|
|
"model_path": "m.onnx",
|
|
"tokenizer_path": "t.json",
|
|
"lib_path": "l.so"
|
|
}
|
|
}
|
|
}`)
|
|
if _, err := Load(p); err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEmbedderPartialConfigRejected(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
json string
|
|
}{
|
|
{"missing model_path", `{"voice":{"enabled":true,"bind":"127.0.0.1:9100","embedder":{"tokenizer_path":"t.json","lib_path":"l.so"}}}`},
|
|
{"missing tokenizer_path", `{"voice":{"enabled":true,"bind":"127.0.0.1:9100","embedder":{"model_path":"m.onnx","lib_path":"l.so"}}}`},
|
|
{"missing lib_path", `{"voice":{"enabled":true,"bind":"127.0.0.1:9100","embedder":{"model_path":"m.onnx","tokenizer_path":"t.json"}}}`},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := writeConfig(t, tt.json)
|
|
if _, err := Load(p); err == nil {
|
|
t.Fatal("Load succeeded for partial embedder; want error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEmbedderNilOK(t *testing.T) {
|
|
// voice block without embedder → ok (HashEmbedder floor)
|
|
p := writeConfig(t, `{"voice":{"enabled":true,"bind":"127.0.0.1:9100"}}`)
|
|
if _, err := Load(p); err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestVoicePresentDisabledOK(t *testing.T) {
|
|
// voice block present but not enabled — acceptable (the listener stays
|
|
// down; the routing table's ChannelVoice selections drop).
|
|
p := writeConfig(t, `{"voice":{"enabled":false}}`)
|
|
if _, err := Load(p); err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestVoiceSttTtsConfigParsed(t *testing.T) {
|
|
// the stt/tts worker sub-blocks parse + remember socket/lang.
|
|
p := writeConfig(t, `{
|
|
"voice": {
|
|
"enabled": true, "bind": "127.0.0.1:9100",
|
|
"stt": {"socket": "/tmp/stt.sock", "lang": "ru"},
|
|
"tts": {"socket": "/tmp/tts.sock", "lang": "ru", "voice": "natasha"}
|
|
}
|
|
}`)
|
|
c, err := Load(p)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if c.Voice.Stt == nil || c.Voice.Stt.Socket != "/tmp/stt.sock" {
|
|
t.Fatalf("Stt config not parsed: %+v", c.Voice)
|
|
}
|
|
if c.Voice.Tts == nil || c.Voice.Tts.Socket != "/tmp/tts.sock" || c.Voice.Tts.Voice != "natasha" {
|
|
t.Fatalf("Tts config not parsed: %+v", c.Voice)
|
|
}
|
|
}
|
|
|
|
func TestWeatherConfig(t *testing.T) {
|
|
// Weather block with provider + default location → OK
|
|
p := writeConfig(t, `{"voice":{"enabled":true,"bind":"127.0.0.1:9100","weather":{"provider":"open-meteo","default_location":"Moscow"}}}`)
|
|
if _, err := Load(p); err != nil {
|
|
t.Fatalf("Load with weather config: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWeatherConfigNilOK(t *testing.T) {
|
|
// No weather block → OK (stub)
|
|
p := writeConfig(t, `{"voice":{"enabled":true,"bind":"127.0.0.1:9100"}}`)
|
|
if _, err := Load(p); err != nil {
|
|
t.Fatalf("Load without weather config: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDurationRoundTrip(t *testing.T) {
|
|
d := Duration(15 * time.Minute)
|
|
b, err := d.MarshalJSON()
|
|
if err != nil {
|
|
t.Fatalf("MarshalJSON: %v", err)
|
|
}
|
|
if got, want := string(b), `"15m0s"`; got != want {
|
|
t.Errorf("MarshalJSON = %s, want %s", got, want)
|
|
}
|
|
var d2 Duration
|
|
if err := d2.UnmarshalJSON(b); err != nil {
|
|
t.Fatalf("UnmarshalJSON: %v", err)
|
|
}
|
|
if d2 != d {
|
|
t.Errorf("round-trip = %v, want %v", d2, d)
|
|
}
|
|
}
|