Files
homelab-frontend/internal/config/models_test.go
T
Admin Bot 9c5fb0ce84
CI / Vet, test, build (push) Canceled after 2m10s
CI / Build and push image (push) Canceled after 0s
feat: load service adapters from ConfigMap, remove k8s API dependency
Adapters defined in config.yaml alongside routes and models.
Parsed by existing config loader, populated into registry at startup.
Removed: client-go deps, REST loader, informer, nginx proxy,
CiliumNetworkPolicy, apis/gateway/v1/ (duplicate types).
Kept: merged CI pipeline, imagePullPolicy Always, CA certs in Dockerfile.
2026-08-26 16:39:30 -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")
}
}