mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-16 19:59:56 +02:00
La façade sert /api/prefs, /api/sysprompt, /api/reasoning-effort et /api/oai/public quand LOKI_SETTINGS=rust (expérimental ; le Go reste le défaut), sur les mêmes clés de la base : préférences fusionnées et nettoyées (prénom borné, avatars en liste fermée, valeurs vides effacées), prompt système du preset actif avec repli sur le global, intensité du raisonnement en liste blanche, adresse publique normalisée. Côté Go, facade_settings.go remet ces routes à la façade quand elles arrivent par le tunnel du relais. Parité : parity_settings_test.go tire du Go 19 adresses, 11 intensités et 29 appels de route avec le contenu de la base après chacun, rejoués par tests/settings.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
184 lines
7.2 KiB
Go
184 lines
7.2 KiB
Go
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// parity_settings_test.go — journal de référence des réglages partagés entre
|
|
// appareils (lot 10 de la migration Rust) : préférences d'affichage, prompt
|
|
// système (par preset, repli global), intensité du raisonnement, adresse
|
|
// publique. Rangé dans rust/loki-rs/tests/parity_settings/settings.json ;
|
|
// tests/settings.rs le rejoue par la façade.
|
|
//
|
|
// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteReglages
|
|
|
|
const paritySettingsFile = "../../rust/loki-rs/tests/parity_settings/settings.json"
|
|
|
|
type paritySettingsStep struct {
|
|
// Avant l'appel : configuration remplacée, preset écrit.
|
|
Config map[string]string `json:"config,omitempty"`
|
|
Preset map[string]string `json:"preset,omitempty"`
|
|
Method string `json:"method"`
|
|
Path string `json:"path"`
|
|
Body string `json:"body,omitempty"`
|
|
Host string `json:"host,omitempty"`
|
|
Status int `json:"status"`
|
|
Out any `json:"out"`
|
|
// Après l'appel : le contenu des clés touchées en base.
|
|
Prefs map[string]string `json:"prefs,omitempty"`
|
|
State map[string]string `json:"state,omitempty"`
|
|
}
|
|
|
|
type paritySettingsFixture struct {
|
|
PublicURL []struct {
|
|
In string `json:"in"`
|
|
Out string `json:"out,omitempty"`
|
|
Err string `json:"err,omitempty"`
|
|
} `json:"public_url"`
|
|
EffortValue map[string]string `json:"effort_value"`
|
|
Steps []paritySettingsStep `json:"steps"`
|
|
}
|
|
|
|
func TestPariteReglages(t *testing.T) {
|
|
testHome(t)
|
|
var fx paritySettingsFixture
|
|
for _, in := range []string{"", " ", "exemple.fr", "https://ia.exemple.fr/", "http://10.0.0.5:8090/v1/",
|
|
"HTTPS://Exemple.FR:443", "ftp://x.fr", "https://", "https://moi:secret@x.fr", "https://x.fr/?a=1",
|
|
"https://x.fr/#ancre", "https://x.fr/api", "https://x.fr:0", "https://x.fr:99999", "https://x.fr:abc",
|
|
"https://[::1]:8090", "https://::1", "https://:8080", "exemple.fr:8090/v1"} {
|
|
c := struct {
|
|
In string `json:"in"`
|
|
Out string `json:"out,omitempty"`
|
|
Err string `json:"err,omitempty"`
|
|
}{In: in}
|
|
out, err := normalizePublicURL(in)
|
|
if err != nil {
|
|
c.Err = err.Error()
|
|
} else {
|
|
c.Out = out
|
|
}
|
|
fx.PublicURL = append(fx.PublicURL, c)
|
|
}
|
|
fx.EffortValue = map[string]string{}
|
|
for _, v := range []string{"", "auto", "Default", "off", "NONE", "0", "low", " High ", "medium", "xhigh", "bizarre"} {
|
|
fx.EffortValue[v] = reasoningEffortValue(v)
|
|
}
|
|
|
|
keys := []string{"sysprompt", "sysprompt:alpha", "sysprompt:beta", "public_url"}
|
|
run := func(st paritySettingsStep) paritySettingsStep {
|
|
if st.Config != nil {
|
|
if err := WriteConfig(st.Config); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for id, content := range st.Preset {
|
|
if err := os.MkdirAll(presetsDir(), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(presetsDir(), id+".env"), []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest(st.Method, st.Path, strings.NewReader(st.Body))
|
|
if st.Host != "" {
|
|
r.Host = st.Host
|
|
}
|
|
switch st.Path {
|
|
case "/api/prefs":
|
|
handleWebPrefs(w, r)
|
|
case "/api/sysprompt":
|
|
handleSysPrompt(w, r)
|
|
case "/api/reasoning-effort":
|
|
handleReasoningEffort(w, r)
|
|
case "/api/oai/public":
|
|
handlePublicAddress(w, r)
|
|
default:
|
|
t.Fatalf("route inattendue %s", st.Path)
|
|
}
|
|
st.Status = w.Code
|
|
if err := json.Unmarshal(w.Body.Bytes(), &st.Out); err != nil {
|
|
t.Fatalf("%s %s : %v (%s)", st.Method, st.Path, err, w.Body.String())
|
|
}
|
|
st.Prefs = loadWebPrefs()
|
|
st.State = map[string]string{}
|
|
for _, k := range keys {
|
|
if v := getStr(bkState, k); v != "" {
|
|
st.State[k] = v
|
|
}
|
|
}
|
|
if v := ReadConfig()["REASONING_EFFORT"]; v != "" {
|
|
st.State["config:REASONING_EFFORT"] = v
|
|
}
|
|
return st
|
|
}
|
|
for _, st := range []paritySettingsStep{
|
|
{Method: "GET", Path: "/api/prefs"},
|
|
{Method: "POST", Path: "/api/prefs", Body: `{"theme":" DARK ","hide_tools":"1","hide_stats":"2","inconnue":"x","user_name":" Ma\u0007rie ","avatar_user":"🦊","avatar_ai":"💩"}`},
|
|
{Method: "POST", Path: "/api/prefs", Body: `{"user_name":"Une très très longue signature éèà","fold_tools":1,"hide_side":"0"}`},
|
|
{Method: "POST", Path: "/api/prefs", Body: `pas du json`},
|
|
{Method: "POST", Path: "/api/prefs", Body: `{"user_name":" ","avatar_user":""}`},
|
|
{Method: "GET", Path: "/api/prefs"},
|
|
// Prompt système : sans preset actif, le global.
|
|
{Method: "GET", Path: "/api/sysprompt"},
|
|
{Method: "POST", Path: "/api/sysprompt", Body: `{"text":" Réponds en français. "}`},
|
|
{Method: "POST", Path: "/api/sysprompt", Body: `{"text":5}`},
|
|
{Method: "PUT", Path: "/api/sysprompt"},
|
|
// Preset alpha actif : son propre prompt, repli sur le global tant qu'il
|
|
// n'en a pas.
|
|
{Config: map[string]string{"MODEL": "a.gguf", "CTX": "8192"},
|
|
Preset: map[string]string{"alpha": "# Alpha\nMODEL=a.gguf\nCTX=8192\n", "beta": "# Beta\nMODEL=b.gguf\n"},
|
|
Method: "GET", Path: "/api/sysprompt"},
|
|
{Method: "POST", Path: "/api/sysprompt", Body: `{"text":"Prompt d'Alpha"}`},
|
|
{Method: "GET", Path: "/api/sysprompt"},
|
|
{Config: map[string]string{"MODEL": "b.gguf"}, Method: "GET", Path: "/api/sysprompt"},
|
|
{Method: "POST", Path: "/api/sysprompt", Body: `{"text":""}`},
|
|
{Config: map[string]string{"MODEL": "a.gguf", "CTX": "8192"}, Method: "POST", Path: "/api/sysprompt", Body: `{"text":" "}`},
|
|
{Method: "GET", Path: "/api/sysprompt"},
|
|
// Intensité du raisonnement.
|
|
{Config: map[string]string{"MODEL": "a.gguf"}, Method: "GET", Path: "/api/reasoning-effort"},
|
|
{Method: "POST", Path: "/api/reasoning-effort", Body: `{"value":" HIGH "}`},
|
|
{Method: "POST", Path: "/api/reasoning-effort", Body: `{"value":"auto"}`},
|
|
{Config: map[string]string{"MODEL": "a.gguf", "REASONING": "on", "REASONING_EFFORT": "off"}, Method: "GET", Path: "/api/reasoning-effort"},
|
|
{Method: "POST", Path: "/api/reasoning-effort", Body: `{"value":""}`},
|
|
{Config: map[string]string{"REASONING": "off", "REASONING_EFFORT": "bizarre"}, Method: "GET", Path: "/api/reasoning-effort"},
|
|
// Adresse publique.
|
|
{Method: "GET", Path: "/api/oai/public"},
|
|
{Method: "POST", Path: "/api/oai/public", Body: `{"url":"ftp://x"}`},
|
|
{Method: "POST", Path: "/api/oai/public", Body: `pas du json`},
|
|
{Method: "POST", Path: "/api/oai/public", Body: `{"url":" ia.exemple.fr/v1 "}`},
|
|
{Method: "GET", Path: "/api/oai/public"},
|
|
{Method: "POST", Path: "/api/oai/public", Body: `{"url":""}`},
|
|
} {
|
|
fx.Steps = append(fx.Steps, run(st))
|
|
}
|
|
|
|
got, err := parityJSON(fx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if os.Getenv("LOKI_PARITY_UPDATE") == "1" {
|
|
if err := os.MkdirAll(filepath.Dir(paritySettingsFile), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(paritySettingsFile, got, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
want, err := os.ReadFile(paritySettingsFile)
|
|
if err != nil {
|
|
t.Fatalf("journal de référence absent (%v) : LOKI_PARITY_UPDATE=1 pour le créer", err)
|
|
}
|
|
if string(got) != string(want) {
|
|
t.Errorf("les réglages ne produisent plus le journal rangé dans %s.\n"+
|
|
"Si c'est voulu : LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteReglages,\n"+
|
|
"puis cargo test --test settings dit si le port Rust suit.\n--- obtenu ---\n%s", paritySettingsFile, got)
|
|
}
|
|
}
|