refactor: switch the whole project to English
talk is going to be published. Code comments, CLI output, error messages,
daemon log lines, MCP descriptions and results, test names and assertions,
README, spec, skill, build scripts and flake are now English. Only the
spoken announcement texts stay bilingual: the wrap result and the test
sentence are German or English depending on the detected language.
Language fallback is now configurable: `lang = "en" | "de"` (env
TALK_LANG, default en) is used when detection cannot tell (numbers, single
words, ties) and for talk's own announcements. langdetect.Detect takes the
fallback as an argument instead of hard-coding German. `talk setup` asks for
it; `talk doctor` and `talk status` show it.
Vocabulary pinned across packages so the process-boundary tests observe
stable strings: chain log lines ("chain: tier X skipped (not available)",
"falling back to the next tier", "spoken via X"), queue lines ("discarded:
quiet hours ... active", "replaced by toast", "max_age"), CLI notes
("speaking directly", "daemon started at", "no daemon", "nothing to cancel"),
status block with aligned English labels, MCP messages, origin guard 403.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4d30bd95ad
commit
85fa64cfe6
95 files changed
+4707
-4854
No files matched your search
+191
-348
@@ -14,7 +14,23 @@ func env(kv map[string]string) func(string) string {
|
||||
return func(k string) string { return kv[k] }
|
||||
}
|
||||
|
||||
func TestDirForPlattformpfade(t *testing.T) {
|
||||
// writeTOML writes content to a temp config.toml and returns its path.
|
||||
func writeTOML(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// missingFile returns a path that does not exist (defaults + env only).
|
||||
func missingFile(t *testing.T) string {
|
||||
t.Helper()
|
||||
return filepath.Join(t.TempDir(), "does-not-exist.toml")
|
||||
}
|
||||
|
||||
func TestDirForPlatformPaths(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
goos string
|
||||
@@ -22,66 +38,26 @@ func TestDirForPlattformpfade(t *testing.T) {
|
||||
want string
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "linux mit HOME",
|
||||
goos: "linux",
|
||||
env: map[string]string{"HOME": "/home/u"},
|
||||
want: "/home/u/.config/talk",
|
||||
},
|
||||
{
|
||||
name: "linux XDG hat Vorrang",
|
||||
goos: "linux",
|
||||
env: map[string]string{"HOME": "/home/u", "XDG_CONFIG_HOME": "/xdg"},
|
||||
want: "/xdg/talk",
|
||||
},
|
||||
{
|
||||
name: "linux relativer XDG wird ignoriert",
|
||||
goos: "linux",
|
||||
env: map[string]string{"HOME": "/home/u", "XDG_CONFIG_HOME": "relativ"},
|
||||
want: "/home/u/.config/talk",
|
||||
},
|
||||
{
|
||||
name: "linux ohne HOME ist Fehler",
|
||||
goos: "linux",
|
||||
env: map[string]string{},
|
||||
errMsg: ErrNoHome.Error(),
|
||||
},
|
||||
{
|
||||
name: "macos",
|
||||
goos: "darwin",
|
||||
env: map[string]string{"HOME": "/Users/u"},
|
||||
want: "/Users/u/Library/Application Support/talk",
|
||||
},
|
||||
{
|
||||
name: "macos ohne HOME ist Fehler",
|
||||
goos: "darwin",
|
||||
env: map[string]string{},
|
||||
errMsg: ErrNoHome.Error(),
|
||||
},
|
||||
{
|
||||
name: "windows",
|
||||
goos: "windows",
|
||||
env: map[string]string{"APPDATA": `C:\Users\u\AppData\Roaming`},
|
||||
want: filepath.Join(`C:\Users\u\AppData\Roaming`, "talk"),
|
||||
},
|
||||
{
|
||||
name: "windows ohne APPDATA ist Fehler",
|
||||
goos: "windows",
|
||||
env: map[string]string{},
|
||||
errMsg: ErrNoHome.Error(),
|
||||
},
|
||||
{"linux with HOME", "linux", map[string]string{"HOME": "/home/u"}, "/home/u/.config/talk", ""},
|
||||
{"linux XDG wins", "linux", map[string]string{"HOME": "/home/u", "XDG_CONFIG_HOME": "/xdg"}, "/xdg/talk", ""},
|
||||
{"linux relative XDG is ignored", "linux", map[string]string{"HOME": "/home/u", "XDG_CONFIG_HOME": "relative"}, "/home/u/.config/talk", ""},
|
||||
{"linux without HOME is an error", "linux", map[string]string{}, "", ErrNoHome.Error()},
|
||||
{"macos", "darwin", map[string]string{"HOME": "/Users/u"}, "/Users/u/Library/Application Support/talk", ""},
|
||||
{"macos without HOME is an error", "darwin", map[string]string{}, "", ErrNoHome.Error()},
|
||||
{"windows", "windows", map[string]string{"APPDATA": `C:\Users\u\AppData\Roaming`}, filepath.Join(`C:\Users\u\AppData\Roaming`, "talk"), ""},
|
||||
{"windows without APPDATA is an error", "windows", map[string]string{}, "", ErrNoHome.Error()},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := dirFor(tc.goos, env(tc.env))
|
||||
if tc.errMsg != "" {
|
||||
if err == nil || err.Error() != tc.errMsg {
|
||||
t.Fatalf("dirFor(%q) Fehler = %v, want %q", tc.goos, err, tc.errMsg)
|
||||
t.Fatalf("dirFor(%q) error = %v, want %q", tc.goos, err, tc.errMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("dirFor(%q): unerwarteter Fehler %v", tc.goos, err)
|
||||
t.Fatalf("dirFor(%q): unexpected error %v", tc.goos, err)
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Errorf("dirFor(%q) = %q, want %q", tc.goos, got, tc.want)
|
||||
@@ -90,58 +66,39 @@ func TestDirForPlattformpfade(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadMerge ist tischgesteuert für die Schichtenfolge
|
||||
// Defaults → TOML-Datei → Env-Override.
|
||||
// TestLoadMerge pins the layering defaults → TOML file → env override.
|
||||
func TestLoadMerge(t *testing.T) {
|
||||
writeTOML := func(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
t.Run("nur Defaults", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("defaults only", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Audio.Sink != SinkOto {
|
||||
t.Errorf("Audio.Sink = %q, want Default %q", cfg.Audio.Sink, SinkOto)
|
||||
t.Errorf("Audio.Sink = %q, want default %q", cfg.Audio.Sink, SinkOto)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TOML überschreibt Default", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[audio]\nsink = \"null\"\n"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("TOML overrides default", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: writeTOML(t, "[audio]\nsink = \"null\"\n"), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Audio.Sink != SinkNull {
|
||||
t.Errorf("Audio.Sink = %q, want %q aus TOML", cfg.Audio.Sink, SinkNull)
|
||||
t.Errorf("Audio.Sink = %q, want %q from TOML", cfg.Audio.Sink, SinkNull)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("leere TOML-Sektion lässt Default stehen", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "# nur ein Kommentar\n[audio]\n"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("empty TOML section keeps default", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: writeTOML(t, "# just a comment\n[audio]\n"), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Audio.Sink != SinkOto {
|
||||
t.Errorf("Audio.Sink = %q, want Default %q", cfg.Audio.Sink, SinkOto)
|
||||
t.Errorf("Audio.Sink = %q, want default %q", cfg.Audio.Sink, SinkOto)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Env schlägt Datei", func(t *testing.T) {
|
||||
t.Run("env beats file", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[audio]\nsink = \"oto\"\n"),
|
||||
Getenv: env(map[string]string{"TALK_AUDIO_SINK": "null"}),
|
||||
@@ -150,15 +107,12 @@ func TestLoadMerge(t *testing.T) {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Audio.Sink != SinkNull {
|
||||
t.Errorf("Audio.Sink = %q, want Env-Override %q", cfg.Audio.Sink, SinkNull)
|
||||
t.Errorf("Audio.Sink = %q, want env override %q", cfg.Audio.Sink, SinkNull)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Env allein ohne Datei", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(map[string]string{"TALK_AUDIO_SINK": "null"}),
|
||||
})
|
||||
t.Run("env alone without file", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(map[string]string{"TALK_AUDIO_SINK": "null"})})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
@@ -167,59 +121,40 @@ func TestLoadMerge(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ungültiger Sink-Wert in Datei ist klarer Fehler", func(t *testing.T) {
|
||||
_, err := Load(Options{
|
||||
File: writeTOML(t, "[audio]\nsink = \"mpv\"\n"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("invalid sink in file is a clear error", func(t *testing.T) {
|
||||
_, err := Load(Options{File: writeTOML(t, "[audio]\nsink = \"mpv\"\n"), Getenv: env(nil)})
|
||||
if err == nil || !strings.Contains(err.Error(), "audio.sink") {
|
||||
t.Fatalf("Load-Fehler = %v, will klaren audio.sink-Fehler", err)
|
||||
t.Fatalf("Load error = %v, want a clear audio.sink error", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ungültiger Env-Wert ist klarer Fehler", func(t *testing.T) {
|
||||
_, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(map[string]string{"TALK_AUDIO_SINK": "bogus"}),
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "TALK") && !strings.Contains(err.Error(), "audio.sink") {
|
||||
t.Fatalf("Load-Fehler = %v, will klaren audio.sink-Fehler", err)
|
||||
t.Run("invalid env value is a clear error", func(t *testing.T) {
|
||||
_, err := Load(Options{File: missingFile(t), Getenv: env(map[string]string{"TALK_AUDIO_SINK": "bogus"})})
|
||||
if err == nil || !strings.Contains(err.Error(), "audio.sink") {
|
||||
t.Fatalf("Load error = %v, want a clear audio.sink error", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("kaputte TOML-Syntax ist klarer Fehler", func(t *testing.T) {
|
||||
_, err := Load(Options{
|
||||
File: writeTOML(t, "[audio\nsink = \n"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Load bei kaputter TOML: kein Fehler")
|
||||
t.Run("broken TOML syntax is a clear error", func(t *testing.T) {
|
||||
if _, err := Load(Options{File: writeTOML(t, "[audio\nsink = \n"), Getenv: env(nil)}); err == nil {
|
||||
t.Fatal("Load with broken TOML: no error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ohne Home läuft Load mit Defaults weiter", func(t *testing.T) {
|
||||
t.Run("without HOME Load continues with defaults", func(t *testing.T) {
|
||||
cfg, err := Load(Options{Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load ohne HOME: %v", err)
|
||||
t.Fatalf("Load without HOME: %v", err)
|
||||
}
|
||||
if cfg.Audio.Sink != SinkOto {
|
||||
t.Errorf("Audio.Sink = %q, want Default %q", cfg.Audio.Sink, SinkOto)
|
||||
t.Errorf("Audio.Sink = %q, want default %q", cfg.Audio.Sink, SinkOto)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestLoadProviderKette deckt die Task-03-Felder ab: Provider-Pin per
|
||||
// Datei/Env, Provider-Endpunkte/-Modelle und das Voice-Mapping.
|
||||
func TestLoadProviderKette(t *testing.T) {
|
||||
writeTOML := func(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// TestLoadProviderChain covers the provider pin (file/env), per-provider
|
||||
// endpoints/models and the voice mapping.
|
||||
func TestLoadProviderChain(t *testing.T) {
|
||||
toml := `provider = "openai"
|
||||
|
||||
[providers.elevenlabs]
|
||||
@@ -233,14 +168,14 @@ model = "gpt-4o-mini-tts"
|
||||
endpoint = "wss://edge.example.com/synth"
|
||||
|
||||
[voice.elevenlabs]
|
||||
de = "MeineStimme"
|
||||
de = "MyGermanVoice"
|
||||
en = "MyVoice"
|
||||
|
||||
[voice.edge]
|
||||
de = "de-DE-KatjaNeural"
|
||||
`
|
||||
|
||||
t.Run("TOML liefert Pin, Endpunkte und Voice-Mapping", func(t *testing.T) {
|
||||
t.Run("TOML yields pin, endpoints and voice mapping", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: writeTOML(t, toml), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
@@ -257,7 +192,7 @@ de = "de-DE-KatjaNeural"
|
||||
if got := cfg.Providers["edge"].Endpoint; got != "wss://edge.example.com/synth" {
|
||||
t.Errorf("Providers[edge].Endpoint = %q", got)
|
||||
}
|
||||
if got := cfg.Voice["elevenlabs"]["de"]; got != "MeineStimme" {
|
||||
if got := cfg.Voice["elevenlabs"]["de"]; got != "MyGermanVoice" {
|
||||
t.Errorf("Voice[elevenlabs][de] = %q", got)
|
||||
}
|
||||
if got := cfg.Voice["elevenlabs"]["en"]; got != "MyVoice" {
|
||||
@@ -265,74 +200,51 @@ de = "de-DE-KatjaNeural"
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TALK_PROVIDER schlägt Datei-Pin", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, toml),
|
||||
Getenv: env(map[string]string{"TALK_PROVIDER": "edge"}),
|
||||
})
|
||||
t.Run("TALK_PROVIDER beats the file pin", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: writeTOML(t, toml), Getenv: env(map[string]string{"TALK_PROVIDER": "edge"})})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Provider != "edge" {
|
||||
t.Errorf("Provider = %q, want Env-Override %q", cfg.Provider, "edge")
|
||||
t.Errorf("Provider = %q, want env override %q", cfg.Provider, "edge")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ohne alles: keine Pins, keine Endpunkte", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("nothing configured: no pin, no endpoints", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Provider != "" || cfg.Providers != nil || cfg.Voice != nil {
|
||||
t.Errorf("Provider-Konfiguration = %+v, will leer", cfg)
|
||||
t.Errorf("provider configuration = %+v, want empty", cfg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestLoadServerPort deckt die Task-04-Felder ab: Daemon-Port per Defaults
|
||||
// (7800), TOML [server] port, Env-Override TALK_PORT samt Validierung.
|
||||
// TestLoadWrapLabel: das Ansage-Label von `talk wrap` (Task 07) —
|
||||
// Defaults → TOML → Env nach dem üblichen Schichten-Muster.
|
||||
// TestLoadWrapLabel: the `talk wrap` announcement label, defaults → TOML →
|
||||
// env in the usual layering.
|
||||
func TestLoadWrapLabel(t *testing.T) {
|
||||
writeTOML := func(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
t.Run("Default leer (Basisname des Befehls)", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("default empty (command base name)", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Wrap.Label != "" {
|
||||
t.Errorf("Wrap.Label = %q, want leer (Default)", cfg.Wrap.Label)
|
||||
t.Errorf("Wrap.Label = %q, want empty (default)", cfg.Wrap.Label)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TOML überschreibt Default", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[wrap]\nlabel = \"Deployment\"\n"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("TOML overrides default", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: writeTOML(t, "[wrap]\nlabel = \"Deployment\"\n"), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Wrap.Label != "Deployment" {
|
||||
t.Errorf("Wrap.Label = %q, want %q aus TOML", cfg.Wrap.Label, "Deployment")
|
||||
t.Errorf("Wrap.Label = %q, want %q from TOML", cfg.Wrap.Label, "Deployment")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TALK_WRAP_LABEL schlägt Datei", func(t *testing.T) {
|
||||
t.Run("TALK_WRAP_LABEL beats the file", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[wrap]\nlabel = \"Deployment\"\n"),
|
||||
Getenv: env(map[string]string{"TALK_WRAP_LABEL": "Nightly Build"}),
|
||||
@@ -341,48 +253,35 @@ func TestLoadWrapLabel(t *testing.T) {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Wrap.Label != "Nightly Build" {
|
||||
t.Errorf("Wrap.Label = %q, want Env-Override %q", cfg.Wrap.Label, "Nightly Build")
|
||||
t.Errorf("Wrap.Label = %q, want env override %q", cfg.Wrap.Label, "Nightly Build")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestLoadServerPort: daemon port by default (7800), TOML [server] port and
|
||||
// env override TALK_PORT including validation.
|
||||
func TestLoadServerPort(t *testing.T) {
|
||||
writeTOML := func(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
t.Run("Default 7800", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("default 7800", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Server.Port != DefaultServerPort {
|
||||
t.Errorf("Server.Port = %d, want Default %d", cfg.Server.Port, DefaultServerPort)
|
||||
t.Errorf("Server.Port = %d, want default %d", cfg.Server.Port, DefaultServerPort)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TOML überschreibt Default", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[server]\nport = 7999\n"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("TOML overrides default", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: writeTOML(t, "[server]\nport = 7999\n"), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Server.Port != 7999 {
|
||||
t.Errorf("Server.Port = %d, want 7999 aus TOML", cfg.Server.Port)
|
||||
t.Errorf("Server.Port = %d, want 7999 from TOML", cfg.Server.Port)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TALK_PORT schlägt Datei", func(t *testing.T) {
|
||||
t.Run("TALK_PORT beats the file", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[server]\nport = 7999\n"),
|
||||
Getenv: env(map[string]string{"TALK_PORT": "7912"}),
|
||||
@@ -391,92 +290,73 @@ func TestLoadServerPort(t *testing.T) {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Server.Port != 7912 {
|
||||
t.Errorf("Server.Port = %d, want Env-Override 7912", cfg.Server.Port)
|
||||
t.Errorf("Server.Port = %d, want env override 7912", cfg.Server.Port)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ungültiger TALK_PORT-Wert ist klarer Fehler", func(t *testing.T) {
|
||||
t.Run("invalid TALK_PORT is a clear error", func(t *testing.T) {
|
||||
for _, bad := range []string{"null", "0", "-1", "70000"} {
|
||||
_, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(map[string]string{"TALK_PORT": bad}),
|
||||
})
|
||||
_, err := Load(Options{File: missingFile(t), Getenv: env(map[string]string{"TALK_PORT": bad})})
|
||||
if err == nil || !strings.Contains(err.Error(), "TALK_PORT") {
|
||||
t.Fatalf("TALK_PORT=%q: Fehler = %v, will klaren TALK_PORT-Fehler", bad, err)
|
||||
t.Fatalf("TALK_PORT=%q: error = %v, want a clear TALK_PORT error", bad, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadAllowedOrigins(t *testing.T) {
|
||||
writeTOML := func(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
t.Run("Default: leere Liste (kein CORS)", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("default: empty list (no CORS)", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if len(cfg.Server.AllowedOrigins) != 0 {
|
||||
t.Errorf("AllowedOrigins = %v, want leer (Tür zu)", cfg.Server.AllowedOrigins)
|
||||
t.Errorf("AllowedOrigins = %v, want empty (door shut)", cfg.Server.AllowedOrigins)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TOML-Liste", func(t *testing.T) {
|
||||
t.Run("TOML list", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[server]\nallowed_origins = [\"https://chat.az-gruppe.com\"]\n"),
|
||||
File: writeTOML(t, "[server]\nallowed_origins = [\"https://chat.example.com\"]\n"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
want := []string{"https://chat.az-gruppe.com"}
|
||||
if strings.Join(cfg.Server.AllowedOrigins, "|") != strings.Join(want, "|") {
|
||||
t.Errorf("AllowedOrigins = %v, want %v", cfg.Server.AllowedOrigins, want)
|
||||
if strings.Join(cfg.Server.AllowedOrigins, "|") != "https://chat.example.com" {
|
||||
t.Errorf("AllowedOrigins = %v", cfg.Server.AllowedOrigins)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TALK_ALLOWED_ORIGINS schlägt Datei (mehrere, mit Leerzeichen)", func(t *testing.T) {
|
||||
t.Run("TALK_ALLOWED_ORIGINS beats the file (several, with spaces)", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[server]\nallowed_origins = [\"https://datei.example\"]\n"),
|
||||
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": "https://chat.az-gruppe.com, https://grid.ag"}),
|
||||
File: writeTOML(t, "[server]\nallowed_origins = [\"https://file.example\"]\n"),
|
||||
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": "https://chat.example.com, https://grid.example"}),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
want := []string{"https://chat.az-gruppe.com", "https://grid.ag"}
|
||||
if strings.Join(cfg.Server.AllowedOrigins, "|") != strings.Join(want, "|") {
|
||||
want := "https://chat.example.com|https://grid.example"
|
||||
if strings.Join(cfg.Server.AllowedOrigins, "|") != want {
|
||||
t.Errorf("AllowedOrigins = %v, want %v", cfg.Server.AllowedOrigins, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TALK_ALLOWED_ORIGINS=off leert die Datei-Liste", func(t *testing.T) {
|
||||
t.Run("TALK_ALLOWED_ORIGINS=off clears the file list", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[server]\nallowed_origins = [\"https://datei.example\"]\n"),
|
||||
File: writeTOML(t, "[server]\nallowed_origins = [\"https://file.example\"]\n"),
|
||||
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": "off"}),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if len(cfg.Server.AllowedOrigins) != 0 {
|
||||
t.Errorf("AllowedOrigins = %v, want leer (off)", cfg.Server.AllowedOrigins)
|
||||
t.Errorf("AllowedOrigins = %v, want empty (off)", cfg.Server.AllowedOrigins)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Wildcard ist erlaubter Wert", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
|
||||
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": "*"}),
|
||||
})
|
||||
t.Run("wildcard is an allowed value", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": "*"})})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
@@ -485,69 +365,53 @@ func TestLoadAllowedOrigins(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("leere Einträge sind klare Fehler", func(t *testing.T) {
|
||||
t.Run("empty entries are clear errors", func(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
file string
|
||||
envv string
|
||||
}{
|
||||
{"env", "", "https://a.example,,https://b.example"},
|
||||
{"datei", "[server]\nallowed_origins = [\"https://a.example\", \"\"]\n", ""},
|
||||
{"file", "[server]\nallowed_origins = [\"https://a.example\", \"\"]\n", ""},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
file := filepath.Join(t.TempDir(), "gibt-es-nicht.toml")
|
||||
file := missingFile(t)
|
||||
if tc.file != "" {
|
||||
file = writeTOML(t, tc.file)
|
||||
}
|
||||
_, err := Load(Options{
|
||||
File: file,
|
||||
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": tc.envv}),
|
||||
})
|
||||
_, err := Load(Options{File: file, Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": tc.envv})})
|
||||
if err == nil || !strings.Contains(err.Error(), "allowed_origins") && !strings.Contains(err.Error(), "ALLOWED_ORIGINS") {
|
||||
t.Fatalf("Fehler = %v, will klaren allowed_origins-Fehler", err)
|
||||
t.Fatalf("error = %v, want a clear allowed_origins error", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestLoadGesetze deckt die Task-05-Felder ab: Rate-Limit (Default 30 s,
|
||||
// "0s" = unbegrenzt), High-Burst (Default an), Ruhezeiten (Default aus;
|
||||
// Format validiert) samt Env-Overrides und klaren Fehlern.
|
||||
func TestLoadGesetze(t *testing.T) {
|
||||
writeTOML := func(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
fehlendeDatei := func(t *testing.T) string {
|
||||
t.Helper()
|
||||
return filepath.Join(t.TempDir(), "gibt-es-nicht.toml")
|
||||
}
|
||||
|
||||
t.Run("Defaults: 30 s, Burst an, Ruhezeit aus, Modus discard", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: fehlendeDatei(t), Getenv: env(nil)})
|
||||
// TestLoadLaws covers rate limit (default 30 s, "0s" = unlimited), high
|
||||
// burst (default on) and quiet hours (default off; format validated) with
|
||||
// env overrides and clear errors.
|
||||
func TestLoadLaws(t *testing.T) {
|
||||
t.Run("defaults: 30 s, burst on, quiet hours off, mode discard", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Queue.RateLimit != DefaultRateLimit {
|
||||
t.Errorf("Queue.RateLimit = %v, want Default %v", cfg.Queue.RateLimit, DefaultRateLimit)
|
||||
t.Errorf("Queue.RateLimit = %v, want default %v", cfg.Queue.RateLimit, DefaultRateLimit)
|
||||
}
|
||||
if !cfg.Queue.AllowHighBurst {
|
||||
t.Error("Queue.AllowHighBurst = false, want Default true")
|
||||
t.Error("Queue.AllowHighBurst = false, want default true")
|
||||
}
|
||||
if cfg.QuietHours != "" {
|
||||
t.Errorf("QuietHours = %q, want Default leer (aus)", cfg.QuietHours)
|
||||
t.Errorf("QuietHours = %q, want default empty (off)", cfg.QuietHours)
|
||||
}
|
||||
if cfg.QuietMode != queue.QuietModeDiscard {
|
||||
t.Errorf("QuietMode = %q, want Default %q", cfg.QuietMode, queue.QuietModeDiscard)
|
||||
t.Errorf("QuietMode = %q, want default %q", cfg.QuietMode, queue.QuietModeDiscard)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("TOML setzt alle Gesetz-Felder", func(t *testing.T) {
|
||||
t.Run("TOML sets all law fields", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, `
|
||||
quiet_hours = "18:00-08:00"
|
||||
@@ -566,36 +430,33 @@ allow_high_burst = false
|
||||
t.Errorf("Queue.RateLimit = %v, want 10s", cfg.Queue.RateLimit)
|
||||
}
|
||||
if cfg.Queue.AllowHighBurst {
|
||||
t.Error("Queue.AllowHighBurst = true, want false aus TOML")
|
||||
t.Error("Queue.AllowHighBurst = true, want false from TOML")
|
||||
}
|
||||
if cfg.QuietHours != "18:00-08:00" {
|
||||
t.Errorf("QuietHours = %q, want Fenster aus TOML", cfg.QuietHours)
|
||||
t.Errorf("QuietHours = %q, want window from TOML", cfg.QuietHours)
|
||||
}
|
||||
if cfg.QuietMode != queue.QuietModeToast {
|
||||
t.Errorf("QuietMode = %q, want toast", cfg.QuietMode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("partielle [queue]-Tabelle behält Defaults", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, "[queue]\nrate_limit = \"0s\"\n"),
|
||||
Getenv: env(nil),
|
||||
})
|
||||
t.Run("partial [queue] table keeps defaults", func(t *testing.T) {
|
||||
cfg, err := Load(Options{File: writeTOML(t, "[queue]\nrate_limit = \"0s\"\n"), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Queue.RateLimit != 0 {
|
||||
t.Errorf("Queue.RateLimit = %v, want 0 (unbegrenzt)", cfg.Queue.RateLimit)
|
||||
t.Errorf("Queue.RateLimit = %v, want 0 (unlimited)", cfg.Queue.RateLimit)
|
||||
}
|
||||
if !cfg.Queue.AllowHighBurst {
|
||||
t.Error("Queue.AllowHighBurst = false, want unveränderten Default true")
|
||||
t.Error("Queue.AllowHighBurst = false, want unchanged default true")
|
||||
}
|
||||
if cfg.QuietMode != queue.QuietModeDiscard {
|
||||
t.Errorf("QuietMode = %q, want unveränderten Default discard", cfg.QuietMode)
|
||||
t.Errorf("QuietMode = %q, want unchanged default discard", cfg.QuietMode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Env überschreibt Datei (inkl. off)", func(t *testing.T) {
|
||||
t.Run("env overrides file (including off)", func(t *testing.T) {
|
||||
cfg, err := Load(Options{
|
||||
File: writeTOML(t, `
|
||||
quiet_hours = "18:00-08:00"
|
||||
@@ -616,96 +477,58 @@ allow_high_burst = false
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Queue.RateLimit != time.Second {
|
||||
t.Errorf("Queue.RateLimit = %v, want 1s aus Env", cfg.Queue.RateLimit)
|
||||
t.Errorf("Queue.RateLimit = %v, want 1s from env", cfg.Queue.RateLimit)
|
||||
}
|
||||
if !cfg.Queue.AllowHighBurst {
|
||||
t.Error("Queue.AllowHighBurst = false, want true aus Env")
|
||||
t.Error("Queue.AllowHighBurst = false, want true from env")
|
||||
}
|
||||
if cfg.QuietHours != "" {
|
||||
t.Errorf("QuietHours = %q, want leer (off aus Env)", cfg.QuietHours)
|
||||
t.Errorf("QuietHours = %q, want empty (off from env)", cfg.QuietHours)
|
||||
}
|
||||
if cfg.QuietMode != queue.QuietModeToast {
|
||||
t.Errorf("QuietMode = %q, want toast aus Env", cfg.QuietMode)
|
||||
t.Errorf("QuietMode = %q, want toast from env", cfg.QuietMode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ungültige Gesetz-Werte sind klare Fehler", func(t *testing.T) {
|
||||
t.Run("invalid law values are clear errors", func(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
toml string
|
||||
envs map[string]string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "TALK_RATE_LIMIT Müll",
|
||||
envs: map[string]string{"TALK_RATE_LIMIT": "schnell"},
|
||||
want: "TALK_RATE_LIMIT",
|
||||
},
|
||||
{
|
||||
name: "TALK_RATE_LIMIT negativ",
|
||||
envs: map[string]string{"TALK_RATE_LIMIT": "-5s"},
|
||||
want: "TALK_RATE_LIMIT",
|
||||
},
|
||||
{
|
||||
name: "rate_limit Müll in Datei",
|
||||
toml: "[queue]\nrate_limit = \"müll\"\n",
|
||||
want: "rate_limit",
|
||||
},
|
||||
{
|
||||
name: "TALK_ALLOW_HIGH_BURST Müll",
|
||||
envs: map[string]string{"TALK_ALLOW_HIGH_BURST": "vielleicht"},
|
||||
want: "TALK_ALLOW_HIGH_BURST",
|
||||
},
|
||||
{
|
||||
name: "quiet_hours Müll in Datei",
|
||||
toml: "quiet_hours = \"abends\"\n",
|
||||
want: "quiet_hours",
|
||||
},
|
||||
{
|
||||
name: "TALK_QUIET_HOURS Müll",
|
||||
envs: map[string]string{"TALK_QUIET_HOURS": "25:00-26:00"},
|
||||
want: "TALK_QUIET_HOURS",
|
||||
},
|
||||
{
|
||||
name: "TALK_QUIET_MODE Müll",
|
||||
envs: map[string]string{"TALK_QUIET_MODE": "vibrieren"},
|
||||
want: "TALK_QUIET_MODE",
|
||||
},
|
||||
{"TALK_RATE_LIMIT garbage", "", map[string]string{"TALK_RATE_LIMIT": "fast"}, "TALK_RATE_LIMIT"},
|
||||
{"TALK_RATE_LIMIT negative", "", map[string]string{"TALK_RATE_LIMIT": "-5s"}, "TALK_RATE_LIMIT"},
|
||||
{"rate_limit garbage in file", "[queue]\nrate_limit = \"garbage\"\n", nil, "rate_limit"},
|
||||
{"TALK_ALLOW_HIGH_BURST garbage", "", map[string]string{"TALK_ALLOW_HIGH_BURST": "maybe"}, "TALK_ALLOW_HIGH_BURST"},
|
||||
{"quiet_hours garbage in file", "quiet_hours = \"evenings\"\n", nil, "quiet_hours"},
|
||||
{"TALK_QUIET_HOURS garbage", "", map[string]string{"TALK_QUIET_HOURS": "25:00-26:00"}, "TALK_QUIET_HOURS"},
|
||||
{"TALK_QUIET_MODE garbage", "", map[string]string{"TALK_QUIET_MODE": "vibrate"}, "TALK_QUIET_MODE"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
file := fehlendeDatei(t)
|
||||
file := missingFile(t)
|
||||
if tc.toml != "" {
|
||||
file = writeTOML(t, tc.toml)
|
||||
}
|
||||
_, err := Load(Options{File: file, Getenv: env(tc.envs)})
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("Fehler = %v, will klaren Fehler mit %q", err, tc.want)
|
||||
t.Fatalf("error = %v, want a clear error mentioning %q", err, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestLoadDaemonAutostart deckt [daemon] autostart ab: Default an, TOML
|
||||
// kann es ausschalten, Env TALK_AUTOSTART schlägt die Datei, Unsinn ist ein
|
||||
// klarer Fehler.
|
||||
// TestLoadDaemonAutostart covers [daemon] autostart: default on, TOML can
|
||||
// switch it off, env TALK_AUTOSTART beats the file, garbage is an error.
|
||||
func TestLoadDaemonAutostart(t *testing.T) {
|
||||
fehlendeDatei := filepath.Join(t.TempDir(), "gibt-es-nicht.toml")
|
||||
writeTOML := func(t *testing.T, content string) string {
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
cfg, err := Load(Options{File: fehlendeDatei, Getenv: env(nil)})
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if !cfg.Daemon.Autostart {
|
||||
t.Error("Daemon.Autostart = false, want Default true")
|
||||
t.Error("Daemon.Autostart = false, want default true")
|
||||
}
|
||||
|
||||
cfg, err = Load(Options{File: writeTOML(t, "[daemon]\nautostart = false\n"), Getenv: env(nil)})
|
||||
@@ -713,7 +536,7 @@ func TestLoadDaemonAutostart(t *testing.T) {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Daemon.Autostart {
|
||||
t.Error("Daemon.Autostart = true, want false aus TOML")
|
||||
t.Error("Daemon.Autostart = true, want false from TOML")
|
||||
}
|
||||
|
||||
cfg, err = Load(Options{
|
||||
@@ -724,33 +547,24 @@ func TestLoadDaemonAutostart(t *testing.T) {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if !cfg.Daemon.Autostart {
|
||||
t.Error("Daemon.Autostart = false, want true aus Env (Env schlägt Datei)")
|
||||
t.Error("Daemon.Autostart = false, want true from env (env beats file)")
|
||||
}
|
||||
|
||||
if _, err := Load(Options{File: fehlendeDatei, Getenv: env(map[string]string{"TALK_AUTOSTART": "vielleicht"})}); err == nil {
|
||||
t.Error("TALK_AUTOSTART=vielleicht: kein Fehler, want klarer Fehler")
|
||||
if _, err := Load(Options{File: missingFile(t), Getenv: env(map[string]string{"TALK_AUTOSTART": "maybe"})}); err == nil {
|
||||
t.Error("TALK_AUTOSTART=maybe: no error, want a clear error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadLimits deckt queue.max_text / queue.max_age ab: Defaults 400 / 2m,
|
||||
// TOML und Env (Env schlägt Datei), 0 = unbegrenzt, Unsinn und negative
|
||||
// Werte sind klare Fehler.
|
||||
// TestLoadLimits covers queue.max_text / queue.max_age: defaults 400 / 2m,
|
||||
// TOML and env (env beats file), 0 = unlimited, garbage and negative values
|
||||
// are clear errors.
|
||||
func TestLoadLimits(t *testing.T) {
|
||||
fehlendeDatei := filepath.Join(t.TempDir(), "gibt-es-nicht.toml")
|
||||
writeTOML := func(t *testing.T, content string) string {
|
||||
path := filepath.Join(t.TempDir(), "config.toml")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
cfg, err := Load(Options{File: fehlendeDatei, Getenv: env(nil)})
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Queue.MaxText != DefaultMaxText || cfg.Queue.MaxAge != DefaultMaxAge {
|
||||
t.Errorf("Defaults = %d/%v, want %d/%v", cfg.Queue.MaxText, cfg.Queue.MaxAge, DefaultMaxText, DefaultMaxAge)
|
||||
t.Errorf("defaults = %d/%v, want %d/%v", cfg.Queue.MaxText, cfg.Queue.MaxAge, DefaultMaxText, DefaultMaxAge)
|
||||
}
|
||||
|
||||
cfg, err = Load(Options{File: writeTOML(t, "[queue]\nmax_text = 0\nmax_age = \"45s\"\n"), Getenv: env(nil)})
|
||||
@@ -769,20 +583,49 @@ func TestLoadLimits(t *testing.T) {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Queue.MaxText != 250 || cfg.Queue.MaxAge != 0 {
|
||||
t.Errorf("Env = %d/%v, want 250/0 (Env schlägt Datei)", cfg.Queue.MaxText, cfg.Queue.MaxAge)
|
||||
t.Errorf("env = %d/%v, want 250/0 (env beats file)", cfg.Queue.MaxText, cfg.Queue.MaxAge)
|
||||
}
|
||||
|
||||
for name, kv := range map[string]map[string]string{
|
||||
"TALK_MAX_TEXT=abc": {"TALK_MAX_TEXT": "abc"},
|
||||
"TALK_MAX_TEXT=-1": {"TALK_MAX_TEXT": "-1"},
|
||||
"TALK_MAX_AGE=bald": {"TALK_MAX_AGE": "bald"},
|
||||
"TALK_MAX_AGE=soon": {"TALK_MAX_AGE": "soon"},
|
||||
"TALK_MAX_AGE=-1s": {"TALK_MAX_AGE": "-1s"},
|
||||
} {
|
||||
if _, err := Load(Options{File: fehlendeDatei, Getenv: env(kv)}); err == nil {
|
||||
t.Errorf("%s: kein Fehler, want klarer Fehler", name)
|
||||
if _, err := Load(Options{File: missingFile(t), Getenv: env(kv)}); err == nil {
|
||||
t.Errorf("%s: no error, want a clear error", name)
|
||||
}
|
||||
}
|
||||
if _, err := Load(Options{File: writeTOML(t, "[queue]\nmax_text = -5\n"), Getenv: env(nil)}); err == nil {
|
||||
t.Error("max_text = -5 in TOML: kein Fehler, want klarer Fehler")
|
||||
t.Error("max_text = -5 in TOML: no error, want a clear error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadLang covers the fallback language: default en, TOML, env
|
||||
// TALK_LANG beats the file, anything but de/en is an error.
|
||||
func TestLoadLang(t *testing.T) {
|
||||
cfg, err := Load(Options{File: missingFile(t), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Lang != "en" {
|
||||
t.Errorf("Lang = %q, want default en", cfg.Lang)
|
||||
}
|
||||
cfg, err = Load(Options{File: writeTOML(t, "lang = \"de\"\n"), Getenv: env(nil)})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Lang != "de" {
|
||||
t.Errorf("Lang = %q, want de from TOML", cfg.Lang)
|
||||
}
|
||||
cfg, err = Load(Options{File: writeTOML(t, "lang = \"de\"\n"), Getenv: env(map[string]string{"TALK_LANG": "en"})})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Lang != "en" {
|
||||
t.Errorf("Lang = %q, want en from env", cfg.Lang)
|
||||
}
|
||||
if _, err := Load(Options{File: missingFile(t), Getenv: env(map[string]string{"TALK_LANG": "fr"})}); err == nil || !strings.Contains(err.Error(), "lang") {
|
||||
t.Errorf("TALK_LANG=fr: error = %v, want a clear lang error", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user