internal/autogen/categoryroots_test.go
Rama main · réplica de lectura del repositorio original.
package autogen
import (
"os"
"path/filepath"
"regexp"
"slices"
"testing"
)
// Platform-neutral half: order, blank-dropping, and exact-duplicate collapse.
// Whether a *differently cased* root is a duplicate depends on the filesystem,
// so that lives in categoryroots_{windows,posix}_test.go.
func TestSettings_RootList_orderAndDedup(t *testing.T) {
s := Settings{
ModelsRoot: `/srv/Models`,
CategoryRoots: map[string]string{
"image": `/srv/Image`,
"tts": ``, // blank dropped
"transcribe": `/srv/Models`, // exact dup of ModelsRoot
},
}
got := s.RootList()
want := []string{`/srv/Models`, `/srv/Image`}
if len(got) != len(want) {
t.Fatalf("RootList = %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("RootList[%d] = %q, want %q (full %v)", i, got[i], want[i], got)
}
}
}
func TestUpsertSidecarRoot_setAndClear(t *testing.T) {
dir := t.TempDir()
gen := filepath.Join(dir, "generate.yaml")
if err := os.WriteFile(gen, []byte("settings:\n modelsRoot: E:/Models\n"), 0o644); err != nil {
t.Fatal(err)
}
if _, err := UpsertSidecarRoot(gen, "image", `E:\Image`); err != nil {
t.Fatal(err)
}
roots, err := LoadSidecarCategoryRoots(gen)
if err != nil {
t.Fatal(err)
}
if roots["image"] != `E:\Image` {
t.Fatalf("after set: roots = %v", roots)
}
// It must surface through the merged generate file too.
gf, err := LoadGenerateFile(gen, "")
if err != nil {
t.Fatal(err)
}
if gf.Settings.CategoryRoots["image"] != `E:\Image` {
t.Fatalf("merged settings missing root: %v", gf.Settings.CategoryRoots)
}
// Clearing (path "") removes the key.
if _, err := UpsertSidecarRoot(gen, "image", ""); err != nil {
t.Fatal(err)
}
roots, _ = LoadSidecarCategoryRoots(gen)
if _, ok := roots["image"]; ok {
t.Fatalf("after clear: key still present: %v", roots)
}
}
// Every UI tab must be walkable. The Models tab renders its folder picker on
// all of them, so a category absent from CategoryOrder is a root the user can
// set and see and that is then never scanned.
func TestSettings_RootList_coversEveryUICategory(t *testing.T) {
roots := map[string]string{}
for _, c := range CategoryOrder {
roots[c] = `/srv/` + c
}
s := Settings{ModelsRoot: `/srv/Models`, CategoryRoots: roots}
got := s.RootList()
if len(got) != len(CategoryOrder)+1 {
t.Fatalf("RootList = %v, want %d entries", got, len(CategoryOrder)+1)
}
for i, c := range CategoryOrder {
if want := `/srv/` + c; got[i+1] != want {
t.Errorf("RootList[%d] = %q, want %q", i+1, got[i+1], want)
}
}
}
// CategoryOrder is the server-side mirror of MODEL_CATEGORIES in the dashboard;
// the pick endpoint rejects anything not in it, so a new UI tab that forgets to
// add itself here would 400 instead of working. Read the real file so the two
// lists cannot drift silently.
func TestCategoryOrder_matchesUITabs(t *testing.T) {
const uiFile = "../../ui-svelte/src/lib/modelUtils.ts"
src, err := os.ReadFile(uiFile)
if err != nil {
t.Skipf("ui sources unavailable: %v", err)
}
block := regexp.MustCompile(`(?s)MODEL_CATEGORIES[^=]*=\s*\[(.*?)\]`).FindSubmatch(src)
if block == nil {
t.Fatalf("could not find MODEL_CATEGORIES in %s", uiFile)
}
var ui []string
for _, m := range regexp.MustCompile(`id:\s*"([^"]+)"`).FindAllSubmatch(block[1], -1) {
ui = append(ui, string(m[1]))
}
if len(ui) == 0 {
t.Fatalf("parsed no category ids from %s", uiFile)
}
if !slices.Equal(ui, CategoryOrder) {
t.Fatalf("CategoryOrder = %v, UI MODEL_CATEGORIES = %v (keep them in sync)", CategoryOrder, ui)
}
}
func TestIsCategory(t *testing.T) {
for _, c := range CategoryOrder {
if !IsCategory(c) {
t.Errorf("IsCategory(%q) = false, want true", c)
}
}
for _, c := range []string{"", "LLM", "audio", "../etc"} {
if IsCategory(c) {
t.Errorf("IsCategory(%q) = true, want false", c)
}
}
}