Lot 21 : mise à jour de Loki tenue par la façade (LOKI_UPDATE=rust)

En conteneur, l'image se met à jour par Docker : /api/update rend la
version du Go sans mise à jour disponible et la note Docker,
/api/update/apply la refuse (409). La façade sert ces deux routes quand
LOKI_UPDATE=rust et LOKI_CONTAINER=1 (sysupdate.rs) ; hors conteneur,
loki-rs ne passe pas la variable et le Go garde le remplacement du
binaire.

Parité : parity_update_test.go tire du Go les quatre réponses en
conteneur, rejouées par tests/sysupdate.rs ; TestFacadeMiseAJour côté Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
Claude committed 2026-10-15 20:01:37 +00:00
1 parent 090f15615e
commit 25e31c1c26
16 files changed
+318 -1

No files matched your search

+4 -1
View File
@@ -150,7 +150,8 @@ var facadeChatRoutes = map[string]bool{
// de la discussion (facade_files.go), les notifications (facade_push.go), la
// mémoire (facade_mem.go), les presets (facade_presets.go), le pilotage du
// moteur (facade_engine.go), les interrupteurs système (facade_system.go), les
// projets (facade_projects.go), les serveurs MCP (facade_mcp.go).
// projets (facade_projects.go), les serveurs MCP (facade_mcp.go), la mise à
// jour (facade_update.go).
// Posé APRÈS requireWebAuth : seule une requête authentifiée ici part, avec le
// jeton interne qui en tient lieu là-bas.
func withFacadeChat(path string, h http.HandlerFunc) http.HandlerFunc {
@@ -179,6 +180,8 @@ func withFacadeChat(path string, h http.HandlerFunc) http.HandlerFunc {
held, unavailable = facadeProjects, "projets indisponibles : "
case facadeMCPRoutes[path]:
held, unavailable = facadeMCP, "serveurs MCP indisponibles : "
case facadeUpdateRoutes[path]:
held, unavailable = facadeUpdate, "mise à jour indisponible : "
default:
return h
}
+22
View File
@@ -905,3 +905,25 @@ func TestFacadeMCP(t *testing.T) {
t.Fatalf("outils sans façade : %v", tools)
}
}
// La mise à jour (LOKI_UPDATE=rust, lot 21) part à la façade.
func TestFacadeMiseAJour(t *testing.T) {
testHome(t)
f := demarrerFacade(t)
t.Setenv(facadeChatEnv, "")
local := func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "go") }
for _, on := range []string{"", "rust"} {
t.Setenv(facadeUpdateEnv, on)
for p := range facadeUpdateRoutes {
w := httptest.NewRecorder()
withFacadeChat(p, local)(w, httptest.NewRequest("GET", p, nil))
want := "go"
if on == "rust" {
want = f.reply
}
if w.Body.String() != want {
t.Fatalf("%s (LOKI_UPDATE=%q) : %q", p, on, w.Body.String())
}
}
}
}
+26
View File
@@ -0,0 +1,26 @@
package loki
import (
"os"
"strings"
)
// facade_update.go — mise à jour de Loki tenue par la façade Rust
// (LOKI_UPDATE=rust, lot 21 de la migration ; expérimental, le Go reste le
// défaut) : /api/update et /api/update/apply. La façade ne tourne qu'en
// conteneur, où l'image se met à jour par Docker : les deux routes le disent
// (containerUpdateNote). Hors conteneur, loki-rs ne passe pas la variable.
const facadeUpdateEnv = "LOKI_UPDATE"
// facadeUpdate : la façade tient-elle la mise à jour ?
func facadeUpdate() bool {
return strings.EqualFold(strings.TrimSpace(os.Getenv(facadeUpdateEnv)), "rust") &&
facadeAddr() != "" && internalToken() != ""
}
// facadeUpdateRoutes : les routes que la façade sert alors (sysupdate.rs).
var facadeUpdateRoutes = map[string]bool{
"/api/update": true,
"/api/update/apply": true,
}
+65
View File
@@ -0,0 +1,65 @@
package loki
import (
"encoding/json"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
// parity_update_test.go — journal de référence de la mise à jour de Loki en
// conteneur (lot 21 de la migration Rust) : l'image se met à jour par Docker,
// les deux routes le disent. Rangé dans
// rust/loki-rs/tests/parity_update/update.json ; tests/sysupdate.rs le rejoue
// par la façade.
//
// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteMiseAJour
const parityUpdateFile = "../../rust/loki-rs/tests/parity_update/update.json"
func TestPariteMiseAJour(t *testing.T) {
testHome(t)
t.Setenv("LOKI_CONTAINER", "1")
var steps []map[string]any
for _, c := range []struct{ method, path, body string }{
{"GET", "/api/update", ""},
{"POST", "/api/update", "{}"},
{"POST", "/api/update/apply", "{}"},
{"GET", "/api/update/apply", ""},
} {
rec := httptest.NewRecorder()
req := httptest.NewRequest(c.method, c.path, strings.NewReader(c.body))
if c.path == "/api/update" {
handleUpdateCheck(rec, req)
} else {
handleUpdateApply(rec, req)
}
var out any
_ = json.Unmarshal([]byte(strings.ReplaceAll(rec.Body.String(), Version, "{{VERSION}}")), &out)
steps = append(steps, map[string]any{"method": c.method, "path": c.path, "status": rec.Code, "out": out})
}
got, err := parityJSON(map[string]any{"steps": steps})
if err != nil {
t.Fatal(err)
}
if os.Getenv("LOKI_PARITY_UPDATE") == "1" {
if err := os.MkdirAll(filepath.Dir(parityUpdateFile), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(parityUpdateFile, got, 0o644); err != nil {
t.Fatal(err)
}
return
}
want, err := os.ReadFile(parityUpdateFile)
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("la mise à jour en conteneur ne produit plus le journal rangé dans %s.\n"+
"Si c'est voulu : LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteMiseAJour.\n--- obtenu ---\n%s",
parityUpdateFile, got)
}
}