feat: add configuration management with YAML parsing and validation

Add internal/config package with Config struct (Server, Navidrome,
MusicBrainz, Telegram, Scanner sections), LoadConfig function for
YAML parsing, config validation (required fields, port range,
threshold range), and defaults. Include config.yaml.example matching
spec. Update main.go to use real config package instead of
placeholders. Add comprehensive tests covering valid configs,
defaults, missing files, malformed YAML, and validation boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 08:47:04 +03:00
parent eb266ed5ec
commit 6ff4ec3045
8 changed files with 564 additions and 72 deletions

View File

@@ -1,43 +1,62 @@
package main
import (
"context"
"os"
"path/filepath"
"testing"
"naviwatcher/internal/config"
)
func TestLoadConfig_Defaults(t *testing.T) {
// The skeleton loadConfig applies defaults when host/port are zero-value.
func TestDefaultConfigPath(t *testing.T) {
if defaultConfigPath != "config.yaml" {
t.Errorf("expected default config path 'config.yaml', got %q", defaultConfigPath)
}
}
func TestRun_GracefulShutdown(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
cfg := &config.Config{}
if err := run(ctx, cfg); err != nil {
t.Fatalf("run returned error: %v", err)
}
}
func TestConfigIntegration(t *testing.T) {
// Integration test: write a minimal valid config and load it via config.LoadConfig,
// verifying the full path that main() uses.
dir := t.TempDir()
path := filepath.Join(dir, "config.yaml")
if err := os.WriteFile(path, []byte("server:\n host: \"\"\n port: 0\n"), 0644); err != nil {
yaml := `server:
host: "127.0.0.1"
port: 9090
navidrome:
url: "http://localhost:4533"
user: "test"
password: "test"
musicbrainz:
user_agent: "NaviWatcher/1.0 ( test@example.com )"
`
if err := os.WriteFile(path, []byte(yaml), 0644); err != nil {
t.Fatalf("failed to write config: %v", err)
}
cfg, err := loadConfig(path)
cfg, err := config.LoadConfig(path)
if err != nil {
t.Fatalf("loadConfig returned error: %v", err)
t.Fatalf("LoadConfig returned error: %v", err)
}
if cfg.Server.Host != "0.0.0.0" {
t.Errorf("expected default host 0.0.0.0, got %q", cfg.Server.Host)
if cfg.Server.Host != "127.0.0.1" {
t.Errorf("expected host 127.0.0.1, got %q", cfg.Server.Host)
}
if cfg.Server.Port != 8080 {
t.Errorf("expected default port 8080, got %d", cfg.Server.Port)
}
}
func TestLoadConfig_FileNotFound(t *testing.T) {
_, err := loadConfig("/nonexistent/path/config.yaml")
if err == nil {
t.Fatal("expected error for missing file, got nil")
}
}
func TestParseYAML_Empty(t *testing.T) {
var cfg placeholderConfig
err := parseYAML([]byte(""), &cfg)
if err != nil {
t.Fatalf("parseYAML returned error for empty input: %v", err)
if cfg.Server.Port != 9090 {
t.Errorf("expected port 9090, got %d", cfg.Server.Port)
}
if cfg.Navidrome.URL != "http://localhost:4533" {
t.Errorf("expected navidrome url http://localhost:4533, got %q", cfg.Navidrome.URL)
}
}