Ctrl K
ComunidadGuías…

internal/autogen/loradir_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package autogen

import (
	"os"
	"path/filepath"
	"testing"
)

// The resolution ladder: per-model override > per-category folder > fleet-wide
// folder > the model gguf's own directory.
func TestResolveLoraDir_ladder(t *testing.T) {
	const modelPath = `D:/LLM/Models/flux1/flux1-dev-Q8_0.gguf`
	full := Settings{
		LoraDir:  `D:/loras/all`,
		LoraDirs: map[string]string{"image": `D:/loras/image`, "video": `D:/loras/video`},
	}
	tests := []struct {
		name     string
		s        Settings
		ov       string
		category string
		want     string
	}{
		{"override wins over everything", full, `D:/loras/mine`, "image", `D:/loras/mine`},
		{"image takes the image folder", full, "", "image", `D:/loras/image`},
		{"video takes the video folder", full, "", "video", `D:/loras/video`},
		{
			"a category with no entry falls back to the fleet-wide folder",
			Settings{LoraDir: `D:/loras/all`, LoraDirs: map[string]string{"image": `D:/loras/image`}},
			"", "video", `D:/loras/all`,
		},
		{
			"a blank entry is not a pin",
			Settings{LoraDir: `D:/loras/all`, LoraDirs: map[string]string{"video": "  "}},
			"", "video", `D:/loras/all`,
		},
		{"nothing set falls back to the model's own dir", Settings{}, "", "image", `D:/LLM/Models/flux1`},
		{
			"a per-category folder alone still beats the model's own dir",
			Settings{LoraDirs: map[string]string{"video": `D:/loras/video`}},
			"", "video", `D:/loras/video`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := resolveLoraDir(tt.s, tt.ov, tt.category, modelPath); got != tt.want {
				t.Fatalf("resolveLoraDir = %q, want %q", got, tt.want)
			}
		})
	}
}

// A nil map must not panic on lookup, which is the shape every pre-existing
// generate file has.
func TestResolveLoraDir_nilMap(t *testing.T) {
	got := resolveLoraDir(Settings{LoraDir: `D:/loras`}, "", "video", `D:/m/x.gguf`)
	if got != `D:/loras` {
		t.Fatalf("resolveLoraDir = %q, want D:/loras", got)
	}
}

func TestLoraCategory(t *testing.T) {
	if got := loraCategory(true); got != "video" {
		t.Errorf("loraCategory(true) = %q, want video", got)
	}
	if got := loraCategory(false); got != "image" {
		t.Errorf("loraCategory(false) = %q, want image", got)
	}
	// Both must be keys the settings API will accept.
	for _, c := range []string{loraCategory(true), loraCategory(false)} {
		if !IsCategory(c) {
			t.Errorf("loraCategory produced %q, which IsCategory rejects", c)
		}
	}
}

// The settings patch is how the dashboard writes these, so a patched map must
// reach Settings intact.
func TestSettingsPatch_loraDirs(t *testing.T) {
	s := Settings{LoraDir: `D:/old`}
	dirs := map[string]string{"video": `D:/loras/video`}
	(&SettingsPatch{LoraDirs: &dirs}).apply(&s)
	if s.LoraDirs["video"] != `D:/loras/video` {
		t.Fatalf("after patch: LoraDirs = %v", s.LoraDirs)
	}
	if s.LoraDir != `D:/old` {
		t.Fatalf("patch clobbered the fleet-wide LoraDir: %q", s.LoraDir)
	}
}

// Saving an unrelated settings section must not drop the per-category folders.
// They are a map, not a pointer, so this exercises MergeSettingsPatch's nil
// carry-forward for non-pointer fields.
func TestMergeSettingsPatch_carriesForwardLoraDirs(t *testing.T) {
	prevDirs := map[string]string{"video": `D:/loras/video`}
	prev := SettingsPatch{LoraDirs: &prevDirs}
	vram := 24.0
	got := MergeSettingsPatch(prev, SettingsPatch{TargetVramGB: &vram})
	if got.LoraDirs == nil || (*got.LoraDirs)["video"] != `D:/loras/video` {
		t.Fatalf("loraDirs dropped by an unrelated save: %v", got.LoraDirs)
	}
	// A non-nil map is still an explicit overwrite (the advanced section
	// rewrites itself wholesale on every save).
	empty := map[string]string{}
	cleared := MergeSettingsPatch(prev, SettingsPatch{LoraDirs: &empty})
	if cleared.LoraDirs == nil || len(*cleared.LoraDirs) != 0 {
		t.Fatalf("explicit clear ignored: %v", cleared.LoraDirs)
	}
}

// The dashboard writes one category at a time, so the helper must be a
// read-modify-write over the single LoraDirs map rather than a plain overwrite.
func TestUpsertSidecarLoraDir_setClearAndPreserve(t *testing.T) {
	dir := t.TempDir()
	gen := filepath.Join(dir, "generate.yaml")
	if err := os.WriteFile(gen, []byte("settings:\n  modelsRoot: D:/Models\n"), 0o644); err != nil {
		t.Fatal(err)
	}

	if err := UpsertSidecarLoraDir(gen, "image", `D:/loras/image`); err != nil {
		t.Fatal(err)
	}
	// Setting a SECOND category must not drop the first.
	if err := UpsertSidecarLoraDir(gen, "video", `D:/loras/video`); err != nil {
		t.Fatal(err)
	}
	gf, err := LoadGenerateFile(gen, "")
	if err != nil {
		t.Fatal(err)
	}
	if gf.Settings.LoraDirs["image"] != `D:/loras/image` || gf.Settings.LoraDirs["video"] != `D:/loras/video` {
		t.Fatalf("after two sets: %v", gf.Settings.LoraDirs)
	}

	// Clearing one leaves the other alone.
	if err := UpsertSidecarLoraDir(gen, "image", ""); err != nil {
		t.Fatal(err)
	}
	gf, err = LoadGenerateFile(gen, "")
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := gf.Settings.LoraDirs["image"]; ok {
		t.Fatalf("image not cleared: %v", gf.Settings.LoraDirs)
	}
	if gf.Settings.LoraDirs["video"] != `D:/loras/video` {
		t.Fatalf("clearing image dropped video: %v", gf.Settings.LoraDirs)
	}
}

// An unrelated settings section saving afterwards must not wipe the folders.
func TestUpsertSidecarLoraDir_survivesAnotherSectionSave(t *testing.T) {
	dir := t.TempDir()
	gen := filepath.Join(dir, "generate.yaml")
	if err := os.WriteFile(gen, []byte("settings:\n  modelsRoot: D:/Models\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := UpsertSidecarLoraDir(gen, "video", `D:/loras/video`); err != nil {
		t.Fatal(err)
	}
	vram := 24.0
	if err := UpsertSidecarSettings(gen, SettingsPatch{TargetVramGB: &vram}); err != nil {
		t.Fatal(err)
	}
	gf, err := LoadGenerateFile(gen, "")
	if err != nil {
		t.Fatal(err)
	}
	if gf.Settings.LoraDirs["video"] != `D:/loras/video` {
		t.Fatalf("a memory-section save dropped the LoRA folder: %v", gf.Settings.LoraDirs)
	}
}