mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-16 03:39:29 +02:00
Le Go expose sa base à la façade par l'API interne : lecture, écriture, suppression d'une clé et lecture d'un bucket entier. Il reste l'unique propriétaire du fichier bbolt ; les écritures passent par putBytes/update et invalident son cache comme une écriture locale. Base illisible : 503, pour que Rust distingue « rien » de « pas pu lire ». Côté Rust : client du store (seul point de passage, que la migration finale de la base ne touchera qu'ici), routeur /api avec contexte partagé et garde d'authentification réutilisable ; /api/ping y emménage. Modules réservés aux aides portées ensuite (système, état du moteur, presets, chemins, GGUF). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
4.1 KiB
Go
133 lines
4.1 KiB
Go
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const jetonTest = "0123456789abcdef0123456789abcdef"
|
|
|
|
func appelInterne(t *testing.T, path, jeton string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
suite := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("mux"))
|
|
})
|
|
r := httptest.NewRequest("GET", path, nil)
|
|
if jeton != "" {
|
|
r.Header.Set(internalTokenHeader, jeton)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
withInternalAPI(suite).ServeHTTP(w, r)
|
|
return w
|
|
}
|
|
|
|
func TestAPIInterneFermeeSansJeton(t *testing.T) {
|
|
t.Setenv(internalTokenEnv, "")
|
|
if w := appelInterne(t, "/internal/v1/auth", jetonTest); w.Code != 404 {
|
|
t.Fatalf("sans jeton configuré : %d, attendu 404", w.Code)
|
|
}
|
|
// Jeton configuré mais trop court : API désactivée, même avec le bon.
|
|
t.Setenv(internalTokenEnv, "court")
|
|
if w := appelInterne(t, "/internal/v1/auth", "court"); w.Code != 404 {
|
|
t.Fatalf("jeton trop court : %d, attendu 404", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestAPIInterneRefuseMauvaisJeton(t *testing.T) {
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
for _, j := range []string{"", "x", strings.ToUpper(jetonTest)} {
|
|
if w := appelInterne(t, "/internal/v1/auth", j); w.Code != 404 {
|
|
t.Fatalf("jeton %q : %d, attendu 404", j, w.Code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAPIInterneAuth(t *testing.T) {
|
|
testHome(t)
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
if err := putStr(bkState, "web_key", "loki-web-secret"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w := appelInterne(t, "/internal/v1/auth", jetonTest)
|
|
if w.Code != 200 {
|
|
t.Fatalf("code %d : %s", w.Code, w.Body.String())
|
|
}
|
|
var got map[string]string
|
|
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got["web_key"] != "loki-web-secret" || got["version"] != Version {
|
|
t.Fatalf("réponse : %+v", got)
|
|
}
|
|
if w := appelInterne(t, "/internal/v1/autre", jetonTest); w.Code != 404 {
|
|
t.Fatalf("route inconnue : %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestAPIInterneLaisseLeResteAuMux(t *testing.T) {
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
if w := appelInterne(t, "/api/ping", ""); w.Body.String() != "mux" {
|
|
t.Fatalf("relais au mux : %q", w.Body.String())
|
|
}
|
|
}
|
|
|
|
func appelStore(t *testing.T, method, path string, body string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
var rd io.Reader
|
|
if body != "" {
|
|
rd = strings.NewReader(body)
|
|
}
|
|
r := httptest.NewRequest(method, path, rd)
|
|
r.Header.Set(internalTokenHeader, jetonTest)
|
|
w := httptest.NewRecorder()
|
|
withInternalAPI(http.NotFoundHandler()).ServeHTTP(w, r)
|
|
return w
|
|
}
|
|
|
|
func TestAPIInterneStore(t *testing.T) {
|
|
testHome(t)
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
if w := appelStore(t, "GET", "/internal/v1/store?bucket=state&key=absente", ""); w.Code != 404 {
|
|
t.Fatalf("clé absente : %d", w.Code)
|
|
}
|
|
if w := appelStore(t, "PUT", "/internal/v1/store?bucket=state&key=k", "valeur\x00binaire"); w.Code != 204 {
|
|
t.Fatalf("put : %d %s", w.Code, w.Body.String())
|
|
}
|
|
if got := getStr(bkState, "k"); got != "valeur\x00binaire" {
|
|
t.Fatalf("écrit : %q", got)
|
|
}
|
|
w := appelStore(t, "GET", "/internal/v1/store?bucket=state&key=k", "")
|
|
if w.Code != 200 || w.Body.String() != "valeur\x00binaire" {
|
|
t.Fatalf("get : %d %q", w.Code, w.Body.String())
|
|
}
|
|
if err := putStr(bkConfig, "PORT", "8081"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w = appelStore(t, "GET", "/internal/v1/store/all?bucket=config", "")
|
|
var m map[string]string
|
|
if err := json.Unmarshal(w.Body.Bytes(), &m); err != nil || m["PORT"] != "8081" {
|
|
t.Fatalf("all : %d %s", w.Code, w.Body.String())
|
|
}
|
|
// Écriture via l'API = écriture locale : le cache de config est à jour.
|
|
_ = ReadConfig()
|
|
if w := appelStore(t, "PUT", "/internal/v1/store?bucket=config&key=PORT", "9000"); w.Code != 204 {
|
|
t.Fatalf("put config : %d", w.Code)
|
|
}
|
|
if LLMPort() != 9000 {
|
|
t.Fatalf("cache non invalidé : port %d", LLMPort())
|
|
}
|
|
if w := appelStore(t, "DELETE", "/internal/v1/store?bucket=state&key=k", ""); w.Code != 204 {
|
|
t.Fatalf("delete : %d", w.Code)
|
|
}
|
|
if getStr(bkState, "k") != "" {
|
|
t.Fatal("clé non supprimée")
|
|
}
|
|
if w := appelStore(t, "GET", "/internal/v1/store?bucket=state", ""); w.Code != 400 {
|
|
t.Fatalf("sans clé : %d", w.Code)
|
|
}
|
|
}
|