287 lines
6.6 KiB
Go
287 lines
6.6 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// validMatrix is a reusable matrix config block for tests.
|
|
const validMatrix = `
|
|
matrix:
|
|
homeserver: "https://matrix.example.org"
|
|
user_id: "@pete:example.org"
|
|
password: "testpass"
|
|
channels:
|
|
tech: "!tech:example.org"
|
|
politics: "!politics:example.org"
|
|
`
|
|
|
|
func TestLoadValid(t *testing.T) {
|
|
yaml := validMatrix + `
|
|
ollama:
|
|
base_url: "http://localhost:11434"
|
|
model: "gemma4:27b"
|
|
timeout_seconds: 60
|
|
storage:
|
|
db_path: "/tmp/test.db"
|
|
sources:
|
|
- name: "Test Source"
|
|
feed_url: "https://example.com/rss"
|
|
tier: 1
|
|
enabled: true
|
|
`
|
|
cfg := loadFromString(t, yaml)
|
|
|
|
if cfg.Matrix.Homeserver != "https://matrix.example.org" {
|
|
t.Errorf("homeserver = %q", cfg.Matrix.Homeserver)
|
|
}
|
|
if cfg.Matrix.UserID != "@pete:example.org" {
|
|
t.Errorf("user_id = %q", cfg.Matrix.UserID)
|
|
}
|
|
if cfg.Ollama.TimeoutSeconds != 60 {
|
|
t.Errorf("timeout = %d, want 60", cfg.Ollama.TimeoutSeconds)
|
|
}
|
|
if len(cfg.Sources) != 1 {
|
|
t.Fatalf("sources = %d, want 1", len(cfg.Sources))
|
|
}
|
|
if cfg.Sources[0].Name != "Test Source" {
|
|
t.Errorf("source name = %q", cfg.Sources[0].Name)
|
|
}
|
|
}
|
|
|
|
func TestEnvVarExpansion(t *testing.T) {
|
|
t.Setenv("TEST_PETE_PASS", "secret_pass_123")
|
|
|
|
yaml := `
|
|
matrix:
|
|
homeserver: "https://matrix.example.org"
|
|
user_id: "@pete:example.org"
|
|
password: "${TEST_PETE_PASS}"
|
|
channels:
|
|
tech: "!tech:example.org"
|
|
ollama:
|
|
base_url: "http://localhost:11434"
|
|
model: "gemma4:27b"
|
|
storage:
|
|
db_path: "/tmp/test.db"
|
|
sources: []
|
|
`
|
|
cfg := loadFromString(t, yaml)
|
|
|
|
if cfg.Matrix.Password != "secret_pass_123" {
|
|
t.Errorf("password = %q, want secret_pass_123", cfg.Matrix.Password)
|
|
}
|
|
}
|
|
|
|
func TestDefaults(t *testing.T) {
|
|
yaml := validMatrix + `
|
|
ollama:
|
|
base_url: "http://localhost:11434"
|
|
model: "gemma4:27b"
|
|
storage:
|
|
db_path: "/tmp/test.db"
|
|
sources:
|
|
- name: "S"
|
|
feed_url: "https://example.com/rss"
|
|
tier: 1
|
|
enabled: true
|
|
`
|
|
cfg := loadFromString(t, yaml)
|
|
|
|
if cfg.Matrix.DataDir != "./data" {
|
|
t.Errorf("data_dir default = %q, want ./data", cfg.Matrix.DataDir)
|
|
}
|
|
if cfg.Matrix.DisplayName != "Pete" {
|
|
t.Errorf("display_name default = %q, want Pete", cfg.Matrix.DisplayName)
|
|
}
|
|
if cfg.Ollama.TimeoutSeconds != 30 {
|
|
t.Errorf("timeout default = %d, want 30", cfg.Ollama.TimeoutSeconds)
|
|
}
|
|
if cfg.Posting.MinIntervalSeconds != 300 {
|
|
t.Errorf("min_interval default = %d, want 300", cfg.Posting.MinIntervalSeconds)
|
|
}
|
|
if cfg.Posting.BurstCapCount != 3 {
|
|
t.Errorf("burst_cap default = %d, want 3", cfg.Posting.BurstCapCount)
|
|
}
|
|
if cfg.Posting.BurstCapWindowSeconds != 1800 {
|
|
t.Errorf("burst_window default = %d, want 1800", cfg.Posting.BurstCapWindowSeconds)
|
|
}
|
|
if cfg.Storage.RecentWindowHours != 24 {
|
|
t.Errorf("recent_window default = %d, want 24", cfg.Storage.RecentWindowHours)
|
|
}
|
|
if cfg.Storage.ClassificationLogDays != 7 {
|
|
t.Errorf("classification_log default = %d, want 7", cfg.Storage.ClassificationLogDays)
|
|
}
|
|
if cfg.Sources[0].PollIntervalMinutes != 20 {
|
|
t.Errorf("poll_interval default = %d, want 20", cfg.Sources[0].PollIntervalMinutes)
|
|
}
|
|
}
|
|
|
|
// shortMatrix returns a minimal valid matrix block for validation error tests.
|
|
func shortMatrix(overrides string) string {
|
|
base := `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
`
|
|
if overrides != "" {
|
|
return overrides
|
|
}
|
|
return base
|
|
}
|
|
|
|
func TestValidationErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
yaml string
|
|
}{
|
|
{"missing homeserver", `
|
|
matrix:
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {base_url: "http://l", model: "m"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
`},
|
|
{"missing user_id", `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {base_url: "http://l", model: "m"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
`},
|
|
{"missing password", `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {base_url: "http://l", model: "m"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
`},
|
|
{"no channels", `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
ollama: {base_url: "http://l", model: "m"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
`},
|
|
{"missing ollama base_url", `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {model: "m"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
`},
|
|
{"missing ollama model", `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {base_url: "http://l"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
`},
|
|
{"missing db_path", `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {base_url: "http://l", model: "m"}
|
|
storage: {}
|
|
`},
|
|
{"invalid tier", `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {base_url: "http://l", model: "m"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
sources:
|
|
- name: "S"
|
|
feed_url: "https://e.com/rss"
|
|
tier: 5
|
|
`},
|
|
{"missing source name", `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {base_url: "http://l", model: "m"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
sources:
|
|
- feed_url: "https://e.com/rss"
|
|
tier: 1
|
|
`},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
os.WriteFile(path, []byte(tt.yaml), 0o644)
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Error("expected validation error, got nil")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDirectRouteNullable(t *testing.T) {
|
|
yaml := `
|
|
matrix:
|
|
homeserver: "https://h"
|
|
user_id: "@p:h"
|
|
password: "pw"
|
|
channels: {tech: "!t:e"}
|
|
ollama: {base_url: "http://l", model: "m"}
|
|
storage: {db_path: "/tmp/t.db"}
|
|
sources:
|
|
- name: "Direct"
|
|
feed_url: "https://e.com/rss"
|
|
tier: 1
|
|
direct_route: "politics"
|
|
enabled: true
|
|
- name: "LLM"
|
|
feed_url: "https://e.com/rss2"
|
|
tier: 1
|
|
direct_route: null
|
|
enabled: true
|
|
`
|
|
cfg := loadFromString(t, yaml)
|
|
|
|
if cfg.Sources[0].DirectRoute == nil {
|
|
t.Fatal("direct source should have non-nil DirectRoute")
|
|
}
|
|
if *cfg.Sources[0].DirectRoute != "politics" {
|
|
t.Errorf("direct_route = %q, want politics", *cfg.Sources[0].DirectRoute)
|
|
}
|
|
if cfg.Sources[1].DirectRoute != nil {
|
|
t.Errorf("null source should have nil DirectRoute, got %v", cfg.Sources[1].DirectRoute)
|
|
}
|
|
}
|
|
|
|
func loadFromString(t *testing.T, yamlContent string) *Config {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
if err := os.WriteFile(path, []byte(yamlContent), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return cfg
|
|
}
|