Add SPECS.md, JSON config loader with env overrides and validation, and an empty tview shell with tabs and status bar. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
79 lines
2.1 KiB
Go
79 lines
2.1 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const valid = `{
|
|
"radarr": {"url": "http://localhost:7878/", "apikey": "r"},
|
|
"sonarr": {"url": "http://localhost:8989", "apikey": "s"},
|
|
"sabnzbd": {"url": "https://sab.example.com", "apikey": "n"}
|
|
}`
|
|
|
|
func TestParseValid(t *testing.T) {
|
|
cfg, err := Parse([]byte(valid))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Radarr.URL != "http://localhost:7878" {
|
|
t.Errorf("trailing slash not trimmed: %q", cfg.Radarr.URL)
|
|
}
|
|
if cfg.UI.RefreshSeconds != defaultRefreshSeconds {
|
|
t.Errorf("refresh default = %d", cfg.UI.RefreshSeconds)
|
|
}
|
|
}
|
|
|
|
func TestParseErrors(t *testing.T) {
|
|
tests := map[string]struct {
|
|
json string
|
|
want string
|
|
}{
|
|
"unknown field": {`{"radar": {}}`, "unknown field"},
|
|
"missing key": {`{"radarr":{"url":"http://a"},"sonarr":{"url":"http://b","apikey":"x"},"sabnzbd":{"url":"http://c","apikey":"x"}}`, "radarr: apikey is required"},
|
|
"bad url": {`{"radarr":{"url":"localhost","apikey":"x"},"sonarr":{"url":"http://b","apikey":"x"},"sabnzbd":{"url":"http://c","apikey":"x"}}`, "must be an http(s) URL"},
|
|
"invalid json": {`{`, "parsing config"},
|
|
}
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
_, err := Parse([]byte(tc.json))
|
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("err = %v, want containing %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnvOverride(t *testing.T) {
|
|
t.Setenv("ARR_TUI_RADARR_APIKEY", "from-env")
|
|
cfg, err := Parse([]byte(valid))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Radarr.APIKey != "from-env" {
|
|
t.Errorf("apikey = %q", cfg.Radarr.APIKey)
|
|
}
|
|
}
|
|
|
|
func TestLoadPermissionWarning(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.json")
|
|
if err := os.WriteFile(path, []byte(valid), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, warnings, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(warnings) != 1 {
|
|
t.Fatalf("warnings = %v", warnings)
|
|
}
|
|
if err := os.Chmod(path, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, warnings, _ = Load(path); len(warnings) != 0 {
|
|
t.Fatalf("unexpected warnings: %v", warnings)
|
|
}
|
|
}
|