mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Bouton « .zip » dans le pied du panneau : la discussion entière — dossiers et sous-dossiers compris — part en une archive, nommée d'après le titre de la discussion. Même danse en trois temps que le téléchargement de fichier (meta → brut, ou tranches base64 derrière le tunnel chiffré), liens symboliques ignorés, parcours borné comme le calcul de taille du panneau. Le scope « hors discussion » a son propre zip, sans le dossier des discussions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
2.5 KiB
Go
97 lines
2.5 KiB
Go
package loki
|
|
|
|
import (
|
|
"archive/zip"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// L'archive embarque TOUT l'arbre — fichiers, dossiers, sous-dossiers — avec
|
|
// des chemins relatifs en avant-slash.
|
|
func TestZipToutLArbre(t *testing.T) {
|
|
ws := withWorkspace(t)
|
|
writeFile(t, filepath.Join(ws, "racine.txt"), "a")
|
|
writeFile(t, filepath.Join(ws, "docs", "note.md"), "b")
|
|
writeFile(t, filepath.Join(ws, "src", "sub", "deep", "main.go"), "c")
|
|
|
|
dest := filepath.Join(t.TempDir(), "out.zip")
|
|
n, err := buildZip(ws, dest, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 3 {
|
|
t.Fatalf("%d fichiers archivés, attendu 3", n)
|
|
}
|
|
zr, err := zip.OpenReader(dest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer zr.Close()
|
|
got := map[string]bool{}
|
|
for _, f := range zr.File {
|
|
got[f.Name] = true
|
|
}
|
|
for _, want := range []string{"racine.txt", "docs/note.md", "src/sub/deep/main.go"} {
|
|
if !got[want] {
|
|
t.Errorf("%s absent de l'archive (contenu : %v)", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Le scope hors-discussion n'aspire pas le dossier des discussions.
|
|
func TestZipLegacyExclutDiscussions(t *testing.T) {
|
|
ws := withWorkspace(t)
|
|
root := agentWorkspace()
|
|
writeFile(t, filepath.Join(root, "orphelin.txt"), "x")
|
|
writeFile(t, filepath.Join(ws, "dans-discussion.txt"), "y") // sous discussions/
|
|
|
|
dest := filepath.Join(t.TempDir(), "out.zip")
|
|
n, err := buildZip(root, dest, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
zr, err := zip.OpenReader(dest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer zr.Close()
|
|
for _, f := range zr.File {
|
|
if f.Name == "discussions/"+filepath.Base(ws)+"/dans-discussion.txt" || filepath.Dir(f.Name) != "." && f.Name[:12] == "discussions/" {
|
|
t.Errorf("le dossier des discussions a fuité dans l'archive : %s", f.Name)
|
|
}
|
|
}
|
|
if n < 1 {
|
|
t.Fatal("orphelin.txt manquant")
|
|
}
|
|
}
|
|
|
|
// Une discussion vide : 404 propre, pas d'archive fantôme.
|
|
func TestZipVide(t *testing.T) {
|
|
ws := withWorkspace(t)
|
|
dest := filepath.Join(t.TempDir(), "out.zip")
|
|
n, err := buildZip(ws, dest, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("%d fichiers dans une discussion vide", n)
|
|
}
|
|
_ = os.Remove(dest)
|
|
}
|
|
|
|
// slugify : titres réels → noms de fichiers sûrs.
|
|
func TestSlugify(t *testing.T) {
|
|
cases := map[string]string{
|
|
"Corrige le bug d'auth": "corrige-le-bug-dauth",
|
|
" Déjà vu ! ": "dj-vu",
|
|
"": "",
|
|
"UPPER case.and.dots": "upper-case-and-dots",
|
|
}
|
|
for in, want := range cases {
|
|
if got := slugify(in); got != want {
|
|
t.Errorf("slugify(%q) = %q, attendu %q", in, got, want)
|
|
}
|
|
}
|
|
}
|