mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki) - LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ; units loki-engine / loki-ui ; binaire et aide CLI - updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les binaires AJEAN amont) Conservé à l'identique : le domaine ajean.link (service de tunnel amont), les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et LICENSE (historique et licence de l'amont). go build/vet/test : verts.
167 lines
5.6 KiB
Go
167 lines
5.6 KiB
Go
package loki
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// Test de la reprise 0.7 → 0.8. À supprimer avec migrate_07.go.
|
|
//
|
|
// Ce code déplace les presets, la mémoire et des modèles de plusieurs dizaines
|
|
// de gigaoctets sur la machine de quelqu'un : il ne peut pas ne pas être testé.
|
|
|
|
// fake07Home fabrique un dossier de données dans l'ancienne disposition.
|
|
func fake07Home(t *testing.T) string {
|
|
t.Helper()
|
|
home := testHome(t)
|
|
stopLegacyUnits = func() {} // pas de systemctl pendant les tests
|
|
t.Cleanup(func() { stopLegacyUnits = stopLegacyUnitsReal })
|
|
|
|
write := func(rel, body string) {
|
|
p := filepath.Join(home, rel)
|
|
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
write("config.env", "MODEL=\"m.gguf\"\nCTX=4096\nBIN=/usr/bin/llama-server\n")
|
|
write("configs/MON PRESET.env", "# NAME=MON PRESET\nMODEL=\"m.gguf\"\nCTX=8192\n")
|
|
write("MEMORY/user-preferences.md", "# Préférences\nfuseau: Europe/Paris\n")
|
|
write("m.gguf", "gguf")
|
|
write(".api_key", "sk-jean-secret\n")
|
|
write(".link_token", "jl_abcdef123456\n")
|
|
write(".agent_enabled", "")
|
|
write("webprefs.json", `{"theme":"dark"}`)
|
|
write("mcp.json", `{"mcpServers":{"fs":{"command":"npx","enabled":true}}}`)
|
|
write("sysprompt.txt", "Tu es Loki.\n")
|
|
write("conversation.json", `{"seq":42,"messages":[{"role":"user","content":"salut"}]}`)
|
|
write("SKILLS/vieux-skill/SKILL.md", "# un skill d'avant\n")
|
|
write(".authorized_users", "AABBCCDDEEFF00112233445566778899AABBCCDDEEFF001122334455667788AA\n")
|
|
return home
|
|
}
|
|
|
|
func TestMigration07(t *testing.T) {
|
|
home := fake07Home(t)
|
|
if !needsMigration07(home) {
|
|
t.Fatal("ancienne disposition non détectée")
|
|
}
|
|
if err := migrateFrom07(home); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Les dossiers ont bougé, pas disparu.
|
|
for _, c := range []struct{ path, want string }{
|
|
{filepath.Join(home, "presets", "MON PRESET.env"), "CTX=8192"},
|
|
{filepath.Join(home, "memory", "user-preferences.md"), "Europe/Paris"},
|
|
{filepath.Join(home, "models", "m.gguf"), "gguf"},
|
|
} {
|
|
b, err := os.ReadFile(c.path)
|
|
if err != nil {
|
|
t.Fatalf("%s absent : %v", c.path, err)
|
|
}
|
|
if !strings.Contains(string(b), c.want) {
|
|
t.Errorf("%s : contenu inattendu %q", c.path, string(b))
|
|
}
|
|
}
|
|
// hasEntry et non os.Stat : sur un système de fichiers insensible à la casse,
|
|
// Stat("MEMORY") réussit sur le dossier « memory » fraîchement créé.
|
|
for _, gone := range []string{"configs", "MEMORY", "m.gguf", "config.env"} {
|
|
if hasEntry(home, gone) {
|
|
t.Errorf("%s traîne encore à la racine", gone)
|
|
}
|
|
}
|
|
|
|
// Les réglages sont en base.
|
|
cfg := ReadConfig()
|
|
if cfg["MODEL"] != "m.gguf" || cfg["CTX"] != "4096" {
|
|
t.Errorf("configuration non reprise : %v", cfg)
|
|
}
|
|
if readAPIKey() != "sk-jean-secret" {
|
|
t.Errorf("clé API = %q", readAPIKey())
|
|
}
|
|
if readLinkToken() != "jl_abcdef123456" {
|
|
t.Errorf("jeton de liaison = %q", readLinkToken())
|
|
}
|
|
if !agentEnabled() {
|
|
t.Error("mode agent perdu")
|
|
}
|
|
if readSysPrompt() != "Tu es Loki." {
|
|
t.Errorf("prompt système = %q", readSysPrompt())
|
|
}
|
|
if loadWebPrefs()["theme"] != "dark" {
|
|
t.Error("préférences perdues")
|
|
}
|
|
if servers, _ := LoadMCPConfig(); servers["fs"].Command != "npx" {
|
|
t.Errorf("serveurs MCP perdus : %v", servers)
|
|
}
|
|
LoadConversation()
|
|
if conv.Seq != 42 {
|
|
t.Errorf("conversation perdue (seq %d)", conv.Seq)
|
|
}
|
|
// Les identités appairées : les perdre casse le chat chiffré du portail
|
|
// sans que rien ne le dise (le tunnel, lui, s'établit normalement).
|
|
// La liste est mise en cache une fois par process (authOnce) : en production
|
|
// c'est un process neuf qui sert après l'install, ici il faut le dire.
|
|
authOnce, authSet = sync.Once{}, map[string]bool{}
|
|
if !isAuthorizedUser("aabbccddeeff00112233445566778899aabbccddeeff001122334455667788aa") {
|
|
t.Error("identité appairée perdue : le chat E2E du portail serait refusé")
|
|
}
|
|
|
|
// Les fichiers consommés sont rangés, pas détruits.
|
|
for _, archive := range []string{"config.env", "SKILLS"} {
|
|
if _, err := os.Stat(filepath.Join(home, "avant-0.8", archive)); err != nil {
|
|
t.Errorf("%s n'a pas été conservé dans avant-0.8/", archive)
|
|
}
|
|
}
|
|
if hasEntry(home, "SKILLS") {
|
|
t.Error("SKILLS/ traîne encore à la racine")
|
|
}
|
|
|
|
// Idempotence : relancer ne doit plus rien voir à migrer.
|
|
if needsMigration07(home) {
|
|
t.Error("la migration se redéclencherait au prochain install")
|
|
}
|
|
if err := migrateFrom07(home); err != nil {
|
|
t.Fatalf("second appel : %v", err)
|
|
}
|
|
}
|
|
|
|
// Une installation 0.8 neuve ne doit rien déclencher.
|
|
func TestMigration07IgnoreUneInstallationNeuve(t *testing.T) {
|
|
home := testHome(t)
|
|
if err := provisionDataDir(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if needsMigration07(home) {
|
|
t.Fatal("migration déclenchée sur une installation neuve")
|
|
}
|
|
}
|
|
|
|
// Machine migrée à la main : les nouveaux dossiers sont remplis ET les anciens
|
|
// traînent encore. L'install ne doit rien écraser, ni échouer.
|
|
func TestMigration07NEcrasePasUnDossierDejaRempli(t *testing.T) {
|
|
home := fake07Home(t)
|
|
precieux := filepath.Join(home, "presets", "DEJA LA.env")
|
|
if err := os.MkdirAll(filepath.Dir(precieux), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(precieux, []byte("CTX=1\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := migrateFrom07(home); err != nil {
|
|
t.Fatalf("la migration doit se poursuivre, pas échouer : %v", err)
|
|
}
|
|
b, err := os.ReadFile(precieux)
|
|
if err != nil || !strings.Contains(string(b), "CTX=1") {
|
|
t.Error("le preset déjà migré a été écrasé")
|
|
}
|
|
if !hasEntry(home, "configs") {
|
|
t.Error("l'ancien dossier a été supprimé alors qu'il fallait le laisser")
|
|
}
|
|
}
|