Files
homelab-frontend/internal/config/models_test.go
T
Story Crater BotandClaude Opus 5 058f11cf2b
CI / Test (push) Canceled after 0s
CI / Vet (push) Canceled after 0s
CI / Build (push) Canceled after 0s
CI / Security (govulncheck) (push) Canceled after 0s
chore: initial commit of Go API gateway
Baseline for the Kong replacement on api.riotpiao.com. Brings the working
tree under version control for the first time: gateway source, the task
board that drives the agent runs, test fixtures, and K8s manifests.

Anchor the gateway ignore rule to the repo root. Unanchored, "gateway"
also matched the cmd/gateway/ source directory, so the program entrypoint
was excluded from every commit.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-19 20:54:34 -07:00

286 lines
6.3 KiB
Go

package config
import (
"os"
"testing"
)
func TestLoadModelsValidConfig(t *testing.T) {
data := `
routes:
- name: test-route
upstream:
address: "localhost:8000"
pathRewrite: ""
connectTimeout: "10s"
readTimeout: "30s"
writeTimeout: "30s"
maxBodySize: 1048576
authRequired: false
models:
- name: "reasoning"
address: "reasoning-predictor.llm-serving:80"
path: "/v1/chat/completions"
- name: "ornith:35b"
address: "ornith-predictor.llm-serving:80"
path: "/v1/chat/completions"
- name: "qwen2.5:3b-instruct"
address: "ornith-predictor.llm-serving:80"
path: "/v1/chat/completions"
`
tmpFile, _ := os.CreateTemp("", "config-*.yaml")
defer os.Remove(tmpFile.Name())
tmpFile.WriteString(data)
tmpFile.Close()
_, models, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
if err != nil {
t.Fatalf("failed to load config: %v", err)
}
if len(models) != 3 {
t.Errorf("expected 3 models, got %d", len(models))
}
// Test LookupModel
cfg := &Config{Models: models}
reasoning := cfg.LookupModel("reasoning")
if reasoning == nil {
t.Errorf("expected to find model 'reasoning'")
}
if reasoning.Address != "reasoning-predictor.llm-serving:80" {
t.Errorf("expected address reasoning-predictor.llm-serving:80, got %s", reasoning.Address)
}
// Test case sensitivity
notFound := cfg.LookupModel("Reasoning")
if notFound != nil {
t.Errorf("model lookup should be case-sensitive")
}
// Test ModelNames
names := cfg.ModelNames()
if len(names) != 3 {
t.Errorf("expected 3 model names, got %d", len(names))
}
}
func TestLoadModelsDuplicateName(t *testing.T) {
data := `
routes:
- name: test-route
upstream:
address: "localhost:8000"
pathRewrite: ""
connectTimeout: "10s"
readTimeout: "30s"
writeTimeout: "30s"
maxBodySize: 1048576
authRequired: false
models:
- name: "reasoning"
address: "upstream1:80"
path: "/v1/chat"
- name: "reasoning"
address: "upstream2:80"
path: "/v1/chat"
`
tmpFile, _ := os.CreateTemp("", "config-*.yaml")
defer os.Remove(tmpFile.Name())
tmpFile.WriteString(data)
tmpFile.Close()
_, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
if err == nil {
t.Errorf("expected error for duplicate model name, got nil")
}
if err.Error() != "duplicate model: \"reasoning\"" {
t.Errorf("expected duplicate model error, got: %v", err)
}
}
func TestLoadModelsEmptyName(t *testing.T) {
data := `
routes:
- name: test-route
upstream:
address: "localhost:8000"
pathRewrite: ""
connectTimeout: "10s"
readTimeout: "30s"
writeTimeout: "30s"
maxBodySize: 1048576
authRequired: false
models:
- name: ""
address: "upstream:80"
path: "/v1/chat"
`
tmpFile, _ := os.CreateTemp("", "config-*.yaml")
defer os.Remove(tmpFile.Name())
tmpFile.WriteString(data)
tmpFile.Close()
_, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
if err == nil {
t.Errorf("expected error for empty model name, got nil")
}
}
func TestLoadModelsMissingAddress(t *testing.T) {
data := `
routes:
- name: test-route
upstream:
address: "localhost:8000"
pathRewrite: ""
connectTimeout: "10s"
readTimeout: "30s"
writeTimeout: "30s"
maxBodySize: 1048576
authRequired: false
models:
- name: "reasoning"
address: ""
path: "/v1/chat"
`
tmpFile, _ := os.CreateTemp("", "config-*.yaml")
defer os.Remove(tmpFile.Name())
tmpFile.WriteString(data)
tmpFile.Close()
_, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
if err == nil {
t.Errorf("expected error for missing address, got nil")
}
if err.Error() != "model \"reasoning\": field 'address' is required" {
t.Errorf("expected missing address error, got: %v", err)
}
}
func TestLoadModelsInvalidAddress(t *testing.T) {
data := `
routes:
- name: test-route
upstream:
address: "localhost:8000"
pathRewrite: ""
connectTimeout: "10s"
readTimeout: "30s"
writeTimeout: "30s"
maxBodySize: 1048576
authRequired: false
models:
- name: "reasoning"
address: "invalid-address-no-port"
path: "/v1/chat"
`
tmpFile, _ := os.CreateTemp("", "config-*.yaml")
defer os.Remove(tmpFile.Name())
tmpFile.WriteString(data)
tmpFile.Close()
_, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
if err == nil {
t.Errorf("expected error for invalid address, got nil")
}
}
func TestLoadModelsOptionalPath(t *testing.T) {
data := `
routes:
- name: test-route
upstream:
address: "localhost:8000"
pathRewrite: ""
connectTimeout: "10s"
readTimeout: "30s"
writeTimeout: "30s"
maxBodySize: 1048576
authRequired: false
models:
- name: "reasoning"
address: "upstream:80"
`
tmpFile, _ := os.CreateTemp("", "config-*.yaml")
defer os.Remove(tmpFile.Name())
tmpFile.WriteString(data)
tmpFile.Close()
_, models, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
if err != nil {
t.Fatalf("failed to load config: %v", err)
}
if len(models) != 1 {
t.Errorf("expected 1 model, got %d", len(models))
}
model := models["reasoning"]
if model.Path != "" {
t.Errorf("expected empty path when not specified, got %s", model.Path)
}
}
func TestModelSameUpstreamMultipleNames(t *testing.T) {
data := `
routes:
- name: test-route
upstream:
address: "localhost:8000"
pathRewrite: ""
connectTimeout: "10s"
readTimeout: "30s"
writeTimeout: "30s"
maxBodySize: 1048576
authRequired: false
models:
- name: "ornith:35b"
address: "ollama-pod:80"
path: "/v1/chat"
- name: "qwen2.5:3b"
address: "ollama-pod:80"
path: "/v1/chat"
`
tmpFile, _ := os.CreateTemp("", "config-*.yaml")
defer os.Remove(tmpFile.Name())
tmpFile.WriteString(data)
tmpFile.Close()
routes, models, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
if err != nil {
t.Fatalf("failed to load config: %v", err)
}
if len(models) != 2 {
t.Errorf("expected 2 models, got %d", len(models))
}
// Both should resolve independently
cfg := &Config{Routes: routes, Models: models}
m1 := cfg.LookupModel("ornith:35b")
m2 := cfg.LookupModel("qwen2.5:3b")
if m1.Address != m2.Address {
t.Errorf("expected both models to point to same upstream")
}
if m1 == m2 {
t.Errorf("expected different ModelUpstream objects even for same address")
}
}