package ajean import ( "os" "path/filepath" "runtime" "testing" ) // mkHome crée un dossier de données factice contenant un fichier témoin, pour // vérifier que la migration déplace bien le CONTENU et pas seulement le dossier. func mkHome(t *testing.T, dir string) { t.Helper() if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "config.env"), []byte("MODEL=x.gguf\n"), 0o644); err != nil { t.Fatal(err) } } // Cas nominal : seul l'ancien dossier existe → il est migré, contenu compris. func TestMigrateHomeMovesLegacy(t *testing.T) { root := t.TempDir() legacy := filepath.Join(root, "jean") target := filepath.Join(root, "ajean") mkHome(t, legacy) if got := migrateHome(target, legacy); got != target { t.Fatalf("got %q, attendu le nouveau chemin %q", got, target) } if b, err := os.ReadFile(filepath.Join(target, "config.env")); err != nil || string(b) != "MODEL=x.gguf\n" { t.Fatalf("contenu non migré: %q (%v)", b, err) } if isDir(legacy) { t.Fatalf("l'ancien dossier existe encore après un rename réussi") } } // Installation neuve : rien à migrer, on renvoie le nouveau chemin sans rien créer. func TestMigrateHomeFreshInstall(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "ajean") if got := migrateHome(target, filepath.Join(root, "jean")); got != target { t.Fatalf("got %q, attendu %q", got, target) } } // Déjà migré : le nouveau dossier existe → on ne retouche à rien, même si un // vieux dossier « jean » traîne encore (utilisateur qui l'a recréé, restauration // de sauvegarde…). Écraser serait perdre les données courantes. func TestMigrateHomeKeepsExistingTarget(t *testing.T) { root := t.TempDir() legacy := filepath.Join(root, "jean") target := filepath.Join(root, "ajean") mkHome(t, legacy) mkHome(t, target) if err := os.WriteFile(filepath.Join(target, "config.env"), []byte("MODEL=courant.gguf\n"), 0o644); err != nil { t.Fatal(err) } if got := migrateHome(target, legacy); got != target { t.Fatalf("got %q, attendu %q", got, target) } b, _ := os.ReadFile(filepath.Join(target, "config.env")) if string(b) != "MODEL=courant.gguf\n" { t.Fatalf("le dossier courant a été écrasé par l'ancien: %q", b) } if !isDir(legacy) { t.Fatalf("l'ancien dossier a été supprimé — la migration ne doit JAMAIS supprimer") } } // Rename impossible → on doit retomber sur l'ancien chemin, intact. C'est la // garantie « au pire, rien n'a bougé », celle qui protège les .gguf. // // On reproduit le scénario réel : un service AJEAN tourne encore et tient un // handle ouvert dans le dossier de données, ce qui fait échouer le rename du // dossier sous Windows. Unix, lui, renomme sans broncher un dossier dont des // fichiers sont ouverts — il n'y a donc rien à simuler là-bas. func TestMigrateHomeFallsBackWhenRenameFails(t *testing.T) { if runtime.GOOS != "windows" { t.Skip("un rename de dossier réussit avec des fichiers ouverts hors Windows") } root := t.TempDir() legacy := filepath.Join(root, "jean") target := filepath.Join(root, "ajean") mkHome(t, legacy) held, err := os.Open(filepath.Join(legacy, "config.env")) if err != nil { t.Fatal(err) } defer held.Close() if got := migrateHome(target, legacy); got != legacy { t.Fatalf("got %q, attendu un repli sur l'ancien chemin %q", got, legacy) } if b, err := os.ReadFile(filepath.Join(legacy, "config.env")); err != nil || string(b) != "MODEL=x.gguf\n" { t.Fatalf("l'ancien dossier a été abîmé par une migration ratée: %q (%v)", b, err) } }