mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Socle du renommage jean -> AJEAN, sans aucun impact visible pour le parc installe. Trois briques : - module github.com/nathaninline/ajean, cmd/ajean, internal/ajean, package ajean. Purement interne. - AjeanHome() remplace JeanHome(). Les defauts deviennent %ProgramData%\ajean et /etc/ajean, avec migration du dossier existant au premier lancement. La migration est un os.Rename dans le dossier parent de l'ancien chemin : intra-volume, donc atomique et instantane meme avec des .gguf de plusieurs dizaines de Go. Si le rename echoue (handle ouvert sous Windows, droits, volume en lecture seule) on CONTINUE sur l'ancien chemin et on retentera : jamais de copie, jamais de suppression, le pire cas est "rien n'a bouge". $AJEAN_HOME et $JEAN_HOME restent tous deux honores, ainsi que /etc/default/ajean et /etc/default/jean : un chemin impose a la main est un ordre, on ne le migre pas. - l'auto-MAJ accepte desormais ajean-<os>-<arch> ET jean-<os>-<arch>, dans cet ordre. Indispensable dans les deux sens : les binaires deja installes ne connaissent que jean-*, et ce binaire-ci doit pouvoir s'installer depuis une release anterieure au renommage. Couvert par des tests, dont le repli quand un handle ouvert bloque le rename sous Windows.
112 lines
3.1 KiB
Go
112 lines
3.1 KiB
Go
package ajean
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestIsHexPub(t *testing.T) {
|
|
ok := strings.Repeat("ab", 32) // 64 hex chars
|
|
cases := map[string]bool{
|
|
ok: true,
|
|
strings.ToUpper(ok): true,
|
|
ok[:62]: false, // trop court
|
|
ok + "cd": false, // trop long
|
|
strings.Repeat("zz", 32): false, // pas hexadécimal
|
|
"": false,
|
|
strings.Repeat("ab", 31) + "g1": false,
|
|
}
|
|
for in, want := range cases {
|
|
if got := isHexPub(in); got != want {
|
|
t.Errorf("isHexPub(%q) = %v, want %v", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHashPairCodeNormalise(t *testing.T) {
|
|
// Le code est normalisé (majuscules + trim) avant hachage : l'utilisateur
|
|
// peut le taper en minuscules ou avec des espaces autour.
|
|
ref := hashPairCode("ABCD-1234")
|
|
for _, v := range []string{"abcd-1234", " ABCD-1234 ", "Abcd-1234\n"} {
|
|
if hashPairCode(v) != ref {
|
|
t.Errorf("hashPairCode(%q) devrait égaler hashPairCode(\"ABCD-1234\")", v)
|
|
}
|
|
}
|
|
if hashPairCode("ABCD-1235") == ref {
|
|
t.Error("codes différents → hashs différents attendus")
|
|
}
|
|
}
|
|
|
|
func TestReplayCheck(t *testing.T) {
|
|
replayMu.Lock()
|
|
replaySeen = map[string]time.Time{}
|
|
replayMu.Unlock()
|
|
|
|
ts := time.Now().UnixMilli()
|
|
if replayCheck("pubA", ts, "iv1") {
|
|
t.Fatal("première vue : ne doit pas être un rejeu")
|
|
}
|
|
if !replayCheck("pubA", ts, "iv1") {
|
|
t.Fatal("même (upub|ts|iv) revu : doit être détecté comme rejeu")
|
|
}
|
|
// Une composante différente = requête distincte, pas un rejeu.
|
|
if replayCheck("pubA", ts, "iv2") || replayCheck("pubB", ts, "iv1") || replayCheck("pubA", ts+1, "iv1") {
|
|
t.Error("clé différente (iv/upub/ts) ne doit pas être vue comme rejeu")
|
|
}
|
|
}
|
|
|
|
func TestReplayCheckPurge(t *testing.T) {
|
|
replayMu.Lock()
|
|
replaySeen = map[string]time.Time{}
|
|
old := time.Now().Add(-3 * e2eAuthWindowMs * time.Millisecond)
|
|
for i := 0; i < 5000; i++ {
|
|
replaySeen[strings.Repeat("x", 8)+string(rune(i))] = old
|
|
}
|
|
replayMu.Unlock()
|
|
|
|
// Au-delà de 4096 entrées, les entrées hors fenêtre sont purgées au passage.
|
|
replayCheck("pub", time.Now().UnixMilli(), "iv")
|
|
replayMu.Lock()
|
|
n := len(replaySeen)
|
|
replayMu.Unlock()
|
|
if n > 10 {
|
|
t.Errorf("purge attendue des vieilles entrées, il en reste %d", n)
|
|
}
|
|
}
|
|
|
|
func TestPairLockout(t *testing.T) {
|
|
pairReset()
|
|
t.Cleanup(pairReset)
|
|
|
|
if pairLocked() {
|
|
t.Fatal("pas de tentative : pas de verrou")
|
|
}
|
|
for i := 0; i < 9; i++ {
|
|
pairRecordFail()
|
|
}
|
|
if pairLocked() {
|
|
t.Fatal("9 échecs : pas encore verrouillé (seuil = 10)")
|
|
}
|
|
pairRecordFail()
|
|
if !pairLocked() {
|
|
t.Fatal("10 échecs : appairage verrouillé attendu")
|
|
}
|
|
// Fenêtre de 5 min expirée → le verrou saute et le compteur repart.
|
|
pairFailMu.Lock()
|
|
pairLockAt = time.Now().Add(-6 * time.Minute)
|
|
pairFailMu.Unlock()
|
|
if pairLocked() {
|
|
t.Fatal("fenêtre expirée : le verrou doit être levé")
|
|
}
|
|
if pairLocked() {
|
|
t.Fatal("après reset du compteur, toujours déverrouillé")
|
|
}
|
|
}
|
|
|
|
func TestE2EAuthAAD(t *testing.T) {
|
|
if string(e2eAuthAAD("pub", 42)) != "pub|42" {
|
|
t.Errorf("AAD inattendu : %q", e2eAuthAAD("pub", 42))
|
|
}
|
|
}
|