Lot 4 (socle) : store accessible à Rust, routeur /api côté façade

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>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-14 17:40:29 +02:00
1 parent a672760f3c
commit bea29bc6a9
14 files changed
+401 -22

No files matched your search

+57
View File
@@ -2,6 +2,7 @@ package loki
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
@@ -73,3 +74,59 @@ func TestAPIInterneLaisseLeResteAuMux(t *testing.T) {
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)
}
}