diff --git a/README.md b/README.md index 6f3c613..62d42d4 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,7 @@ redémarrages (volume `/data`). Variables d'environnement du conteneur : | `LOKI_MEM` | `rust` : la mémoire (éditeur des pages, index, déplacement entre projets, trackers, mode mémoire, interrupteurs du mode agent) est tenue par la façade Rust ; le chiffrement reste au Go (expérimental, migration en cours) | Go | | `LOKI_SERVE` | `rust` : la ligne de commande de llama-server est composée par la façade Rust (`loki-rs engine serve`) au lieu de `loki serve` ; Strata et l'optimiseur restent au Go (expérimental, migration en cours) | Go | | `LOKI_PRESETS` | `rust` : les presets (liste, éditeur et aperçus, suppression, placement auto, presets externes, bascule) sont tenus par la façade Rust ; l'application au moteur reste au Go (expérimental, migration en cours) | Go | +| `LOKI_PROJECTS` | `rust` : le hub des projets (liste et comptes, création, renommage, description, suppression, bascule) est tenu par la façade Rust ; les discussions restent à qui tient la conversation (expérimental, migration en cours) | Go | | `LOKI_SYSTEM` | `rust` : les interrupteurs système — accès internet et Crawl4AI, pilotage du navigateur, clé de complétion, écoute réseau du moteur — sont tenus par la façade Rust ; le navigateur piloté reste au Go (expérimental, migration en cours) | Go | | `LOKI_ENGINE` | `rust` : le pilotage du moteur (démarrage, arrêt, redémarrage, déchargement et rechargement de la VRAM) est tenu par la façade Rust, en conteneur avec son superviseur ; le verrou de l'optimiseur reste au Go (expérimental, migration en cours) | Go | diff --git a/docs/superpowers/specs/2026-10-15-rust-migration-lot18-projets-design.md b/docs/superpowers/specs/2026-10-15-rust-migration-lot18-projets-design.md new file mode 100644 index 0000000..9790ded --- /dev/null +++ b/docs/superpowers/specs/2026-10-15-rust-migration-lot18-projets-design.md @@ -0,0 +1,78 @@ +# Lot 18 — hub des projets tenu par la façade + +## Contexte + +Le hub des projets vivait dans le Go : +- `handleProjects` et `projectDTOs` (`web_projects.go`) ; +- les mutations de `projects.go` : `createProject`, `renameProject`, + `setProjectDesc`, `deleteProject` ; +- `projectSwitch` (`chat_sessions.go`). + +Lot 18 : la façade tient `/api/projects` quand `LOKI_PROJECTS=rust` +(`projects_api.rs`, expérimental). Sans la variable, rien ne change. + +| Action | Rôle | +|---|---| +| `GET` | les projets, le plus ancien d'abord, avec leurs discussions, pages (`.md` visibles, index exclu) et trackers comptés ; le projet actif | +| `create` | slug translittéré (`Réseau NAS` → `reseau-nas`), unique (`-2`, `-3`…), `projet` à défaut, 40 octets au plus ; nom de 60 runes au plus ; dossier `memory/` et son index amorcé | +| `rename` | le libellé seul : slug, chemins et discussions ne bougent pas | +| `desc` | 2 000 runes au plus, vide = effacée | +| `switch` | la conversation passe sur le projet (la plus récente de ses discussions, ou une neuve) | +| `delete` | discussions, trackers et mémoire du projet ; le projet actif est réassigné ; le dernier projet est indestructible | + +Le registre reste dans la base (`state/projects`, écrit comme `json.Marshal` +avec le HTML échappé), la mémoire de chaque projet dans `memory/`. + +## Restent au Go + +Le projet actif passe toujours par le Go (`/internal/v1/project/activate`), +qui en garde une copie en RAM pour la mémoire, les outils et les tâches. + +Les discussions sont à qui tient la conversation. + +Quand la façade la tient (`LOKI_CHAT=rust`), elle bascule et supprime +elle-même. Avant de supprimer le projet actif, elle enregistre sa discussion +et en ouvre une du premier projet restant. + +Sinon le Go garde deux routes internes (`facade_projects.go`) : +- `POST /internal/v1/project/switch` : `projectSwitch` (discussion du projet + quitté enregistrée, plus récente du projet rejoint ouverte, ou une neuve) ; +- `POST /internal/v1/project/drop-convs` : les discussions du projet + supprimées (`convDelete`, fichiers et résultats compris). + +Une génération en cours refuse la bascule et la suppression (409). La façade +lit la sienne, ou celle du Go (`/internal/v1/chat/generating`, lot 16). + +## Écarts assumés + +- Pendant une tâche planifiée, le Go voit comme actif le projet de la tâche ; + la façade lit le projet actif enregistré. +- Pendant un tour de la façade, une bascule vers un projet inconnu répond 409, + comme quand le Go tient la conversation. Le Go, relayant à la façade, + répondait 400 (« projet introuvable ») dans ce cas. + +## Parité + +`internal/loki/parity_projects_test.go` part d'une base amorcée : +- pages visibles, cachées, hors `.md`, dans un sous-dossier ; +- discussions de Générale, d'avant les projets, d'un projet à venir et d'un + projet disparu ; +- trackers, dont `alphabet/x` qui ne doit pas tomber avec `alpha`. + +Il tire du Go 40 appels, avec après chacun le registre, le projet actif, +l'arbre `memory/`, les trackers et l'index des discussions. Ils couvrent : +- créations (doublons, translittération, sans lettres, longues, vides) ; +- renommages et descriptions (longues, coupées sur une espace, effacées) ; +- bascules (sur l'actif, inconnue, vers un projet sans discussion, pendant + une génération) ; +- suppressions (de l'actif, inconnue, pendant une génération, jusqu'au + dernier projet) ; +- actions inconnues, corps vide ou illisible, erreur de type, méthode + refusée. + +`tests/projects_api.rs` rejoue le tout par la façade, contre un faux Go qui +bascule et supprime les discussions comme le vrai. La bascule et la +suppression quand la façade tient la conversation sont vérifiées de bout en +bout. Une mutation de chaque règle fait échouer les tests. + +Côté Go, `TestFacadeProjets` vérifie le relais et les deux routes internes. diff --git a/internal/loki/facade_chat.go b/internal/loki/facade_chat.go index 61f9793..69a6311 100644 --- a/internal/loki/facade_chat.go +++ b/internal/loki/facade_chat.go @@ -145,7 +145,8 @@ var facadeChatRoutes = map[string]bool{ // (facade_dictate.go), les réglages partagés (facade_settings.go), les fichiers // 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). +// moteur (facade_engine.go), les interrupteurs système (facade_system.go), les +// projets (facade_projects.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 { @@ -170,6 +171,8 @@ func withFacadeChat(path string, h http.HandlerFunc) http.HandlerFunc { held, unavailable = facadeEngine, "pilotage du moteur indisponible : " case facadeSystemRoutes[path]: held, unavailable = facadeSystem, "réglages système indisponibles : " + case facadeProjectsRoutes[path]: + held, unavailable = facadeProjects, "projets indisponibles : " default: return h } diff --git a/internal/loki/facade_chat_test.go b/internal/loki/facade_chat_test.go index 3f7e05e..4946f7c 100644 --- a/internal/loki/facade_chat_test.go +++ b/internal/loki/facade_chat_test.go @@ -758,3 +758,72 @@ func TestFacadeRelaisGardeHote(t *testing.T) { t.Fatalf("hôte %q, schéma %q", host, proto) } } + +// Le hub des projets (LOKI_PROJECTS=rust, lot 18) part à la façade ; quand le +// Go tient la conversation, la bascule et les discussions d'un projet +// supprimé restent ici (API interne). +func TestFacadeProjets(t *testing.T) { + testHome(t) + // Le projet actif est gardé en RAM d'un test à l'autre. + activeProjMu.Lock() + activeProjCache = "" + activeProjMu.Unlock() + t.Cleanup(func() { + activeProjMu.Lock() + activeProjCache = "" + activeProjMu.Unlock() + }) + ensureDefaultProject() + f := demarrerFacade(t) + t.Setenv(facadeChatEnv, "") + local := func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "go") } + w := httptest.NewRecorder() + withFacadeChat("/api/projects", local)(w, httptest.NewRequest("GET", "/api/projects", nil)) + if w.Body.String() != "go" { + t.Fatalf("servi ailleurs sans LOKI_PROJECTS : %q", w.Body.String()) + } + t.Setenv(facadeProjectsEnv, "rust") + w = httptest.NewRecorder() + withFacadeChat("/api/projects", local)(w, httptest.NewRequest("POST", "/api/projects", strings.NewReader("{}"))) + if w.Body.String() != f.reply || len(f.recu()) != 1 { + t.Fatalf("relais : %q, %d appels", w.Body.String(), len(f.recu())) + } + + if _, err := createProject("Alpha"); err != nil { + t.Fatal(err) + } + convIndexSave([]convMeta{{ID: "a1", Project: "alpha", Updated: 1}, {ID: "g1", Project: "generale", Updated: 2}}) + _ = putBytes(bkChat, convKey("a1"), []byte(`{"id":"a1"}`)) + _ = putBytes(bkChat, convKey("g1"), []byte(`{"id":"g1"}`)) + post := func(h http.HandlerFunc, body string) (int, string) { + w := httptest.NewRecorder() + h(w, httptest.NewRequest("POST", "/internal/v1/project/x", strings.NewReader(body))) + return w.Code, strings.TrimSpace(w.Body.String()) + } + if c, b := post(handleInternalProjectSwitch, `{"slug":"nope"}`); c != 400 || b != `{"error":"projet introuvable"}` { + t.Fatalf("bascule inconnue : %d %s", c, b) + } + if c, _ := post(handleInternalProjectSwitch, `{"slug":" alpha "}`); c != 204 || activeProjectSlug() != "alpha" { + t.Fatalf("bascule : %d, actif %q", c, activeProjectSlug()) + } + conv.mu.Lock() + conv.Generating = true + conv.mu.Unlock() + c1, _ := post(handleInternalProjectSwitch, `{"slug":"generale"}`) + c2, _ := post(handleInternalProjectDropConvs, `{"slug":"alpha"}`) + conv.mu.Lock() + conv.Generating = false + conv.mu.Unlock() + if c1 != 409 || c2 != 409 { + t.Fatalf("pendant une génération : %d, %d", c1, c2) + } + if c, b := post(handleInternalProjectDropConvs, `{"slug":"alpha"}`); c != 200 || b != `{"dropped":1}` { + t.Fatalf("discussions supprimées : %d %s", c, b) + } + if n := len(convIndexForProject("alpha")); n != 0 || len(convIndexForProject("generale")) == 0 { + t.Fatalf("index après : %d d'Alpha", n) + } + if c, _ := post(handleInternalProjectDropConvs, `nimp`); c != 400 { + t.Fatalf("corps illisible : %d", c) + } +} diff --git a/internal/loki/facade_projects.go b/internal/loki/facade_projects.go new file mode 100644 index 0000000..f96dcb8 --- /dev/null +++ b/internal/loki/facade_projects.go @@ -0,0 +1,77 @@ +package loki + +import ( + "net/http" + "os" + "strings" +) + +// facade_projects.go — hub des projets tenu par la façade Rust +// (LOKI_PROJECTS=rust, lot 18 de la migration ; expérimental, le Go reste le +// défaut) : liste avec ses comptes, création, renommage, description, +// suppression, bascule (/api/projects). +// +// Le registre reste dans la base (state/projects) et la mémoire de chaque +// projet dans memory/. Le projet actif passe toujours par le Go +// (/internal/v1/project/activate), qui en garde une copie en RAM. Quand le Go +// tient la conversation, ce qui la touche reste ici : +// +// - la bascule : discussion du projet quitté enregistrée, la plus récente +// du projet rejoint ouverte (/internal/v1/project/switch) ; +// - les discussions d'un projet supprimé (/internal/v1/project/drop-convs). +// +// Quand la façade tient la conversation, elle fait les deux elle-même. + +const facadeProjectsEnv = "LOKI_PROJECTS" + +// facadeProjects : la façade tient-elle le hub des projets ? +func facadeProjects() bool { + return strings.EqualFold(strings.TrimSpace(os.Getenv(facadeProjectsEnv)), "rust") && + facadeAddr() != "" && internalToken() != "" +} + +// facadeProjectsRoutes : les routes que la façade sert alors (projects_api.rs). +var facadeProjectsRoutes = map[string]bool{ + "/api/projects": true, +} + +// handleInternalProjectSwitch : POST {slug} → 204, ou 400/409 + {error}. +func handleInternalProjectSwitch(w http.ResponseWriter, r *http.Request) { + var in struct { + Slug string `json:"slug"` + } + if !readInternalJSON(w, r, &in) { + return + } + if conv.isGenerating() { + sendJSON(w, http.StatusConflict, map[string]any{"error": "une génération est en cours"}) + return + } + if err := projectSwitch(strings.TrimSpace(in.Slug)); err != nil { + sendJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) + return + } + w.WriteHeader(http.StatusNoContent) +} + +// handleInternalProjectDropConvs : POST {slug} → 200 {dropped} — supprime les +// discussions du projet, comme deleteProject quand le Go tient la conversation. +func handleInternalProjectDropConvs(w http.ResponseWriter, r *http.Request) { + var in struct { + Slug string `json:"slug"` + } + if !readInternalJSON(w, r, &in) { + return + } + if conv.isGenerating() { + sendJSON(w, http.StatusConflict, map[string]any{"error": "une génération est en cours"}) + return + } + n := 0 + for _, m := range convIndexForProject(strings.TrimSpace(in.Slug)) { + if convDelete(m.ID) == nil { + n++ + } + } + sendJSON(w, http.StatusOK, map[string]any{"dropped": n}) +} diff --git a/internal/loki/parity_projects_test.go b/internal/loki/parity_projects_test.go new file mode 100644 index 0000000..0bd61f2 --- /dev/null +++ b/internal/loki/parity_projects_test.go @@ -0,0 +1,272 @@ +//go:build linux + +package loki + +import ( + "encoding/json" + "net/http/httptest" + "os" + "path/filepath" + "sort" + "strings" + "testing" +) + +// parity_projects_test.go — journal de référence du hub des projets (lot 18 +// de la migration Rust) : liste et comptes (discussions, pages, trackers), +// création (slug translittéré, unique, tronqué), renommage, description, +// bascule et suppression (discussions, trackers et mémoire du projet, actif +// réassigné, dernier projet indestructible), refus pendant une génération, +// erreurs JSON. Après chaque appel : le registre, le projet actif, l'arbre +// memory/, les trackers et l'index des discussions. La conversation est tenue +// par le Go (le Rust le rejoue contre le faux Go, qui fait de même). +// Rangé dans rust/loki-rs/tests/parity_projects/projects.json ; +// tests/projects_api.rs le rejoue par la façade. +// +// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteProjets + +const parityProjectsFile = "../../rust/loki-rs/tests/parity_projects/projects.json" + +type parityProjectStep struct { + Name string `json:"name"` + Generating bool `json:"generating,omitempty"` + Method string `json:"method"` + Body string `json:"body,omitempty"` + Status int `json:"status"` + Out any `json:"out"` + After map[string]any `json:"after"` +} + +// parityZeroTimes : les dates de création, propres à chaque exécution. +func parityZeroTimes(v any) any { + switch t := v.(type) { + case map[string]any: + for k, x := range t { + if k == "created" || k == "created_at" || k == "updated" { + t[k] = 0 + } else { + t[k] = parityZeroTimes(x) + } + } + case []any: + for i := range t { + t[i] = parityZeroTimes(t[i]) + } + } + return v +} + +func TestPariteProjets(t *testing.T) { + home := testHome(t) + ensureDefaultProject() + // Le projet actif est gardé en RAM d'un test à l'autre. + activeProjMu.Lock() + activeProjCache = "" + activeProjMu.Unlock() + t.Cleanup(func() { + activeProjMu.Lock() + activeProjCache = "" + activeProjMu.Unlock() + }) + // Date de création fixe : la graine est rangée telle quelle. + if err := saveProjects([]Project{{Slug: defaultProjectSlug, Name: defaultProjectName, CreatedAt: 1}}); err != nil { + t.Fatal(err) + } + // Pages de Générale : trois comptées, le reste non. + for _, f := range []string{"a.md", "b.MD", "z.md", ".cache.md", "notes.txt", "sous/page.md"} { + p := filepath.Join(projectMemoryDir(defaultProjectSlug), f) + _ = os.MkdirAll(filepath.Dir(p), 0o755) + if err := os.WriteFile(p, []byte("# "+f+"\n"), 0o600); err != nil { + t.Fatal(err) + } + } + // Discussions : deux de Générale (dont une d'avant les projets), trois + // d'Alpha (créé plus loin), une d'un projet disparu. + idx := []convMeta{ + {ID: "g1", Title: "g1", Created: 1, Updated: 10, Project: "generale"}, + {ID: "o1", Title: "o1", Created: 2, Updated: 20}, + {ID: "a1", Title: "a1", Created: 3, Updated: 30, Project: "alpha"}, + {ID: "a2", Title: "a2", Created: 4, Updated: 50, Project: "alpha"}, + {ID: "a3", Title: "a3", Created: 5, Updated: 40, Project: "alpha"}, + {ID: "x1", Title: "x1", Created: 6, Updated: 60, Project: "ailleurs"}, + } + convIndexSave(idx) + for _, m := range idx { + _ = putBytes(bkChat, convKey(m.ID), []byte(`{"id":"`+m.ID+`","log":[]}`)) + } + // La discussion ouverte, comme au démarrage du Go (convEnsureActive). + _ = putStr(bkChat, ckActive, "g1") + convActivate("g1", getBytes(bkChat, convKey("g1"))) + seen := map[string]bool{} + for _, m := range idx { + seen[m.ID] = true + } + for _, k := range []string{"generale/poids", "alpha/sommeil", "alpha/pas", "alphabet/x"} { + _ = putStr(bkTracker, k, `{"name":"`+k+`","events":[]}`) + } + + snapshot := func() map[string]any { + st := map[string]any{} + var reg any + _ = json.Unmarshal(getBytes(bkState, stProjectsKey), ®) + st["projects"] = parityZeroTimes(reg) + st["active"] = getStr(bkState, stActiveProjectKey) + var tree []string + _ = filepath.Walk(filepath.Join(home, "memory"), func(p string, fi os.FileInfo, err error) error { + if err != nil { + return nil + } + rel, _ := filepath.Rel(home, p) + if fi.IsDir() { + rel += "/" + } + tree = append(tree, rel) + return nil + }) + sort.Strings(tree) + st["tree"] = tree + var trk []string + for k := range allKV(bkTracker) { + trk = append(trk, k) + } + sort.Strings(trk) + st["trackers"] = trk + var convs []string + for _, m := range convIndex() { + id := m.ID + if !seen[id] { + id = "{{NEW}}" // discussion créée par la bascule, identifiant tiré au hasard + } + convs = append(convs, id+":"+m.Project) + } + sort.Strings(convs) + st["convs"] = convs + return st + } + seed := snapshot() + seedMem := map[string]string{} + _ = filepath.Walk(filepath.Join(home, "memory"), func(p string, fi os.FileInfo, err error) error { + if err == nil && !fi.IsDir() { + b, _ := os.ReadFile(p) + rel, _ := filepath.Rel(home, p) + seedMem[rel] = string(b) + } + return nil + }) + seedStore := map[string]string{ + "state/projects": string(getBytes(bkState, stProjectsKey)), + "state/active_project": getStr(bkState, stActiveProjectKey), + "chat/index": string(getBytes(bkChat, ckIndex)), + } + for _, m := range idx { + seedStore["chat/"+convKey(m.ID)] = string(getBytes(bkChat, convKey(m.ID))) + } + for k, v := range allKV(bkTracker) { + seedStore["tracker/"+k] = string(v) + } + + long := strings.Repeat("é", 2100) + " fin" + var steps []parityProjectStep + for _, st := range []parityProjectStep{ + {Name: "liste", Method: "GET"}, + {Name: "création", Method: "POST", Body: `{"action":"create","name":"Alpha"}`}, + {Name: "création, même nom", Method: "POST", Body: `{"action":"create","name":"Alpha"}`}, + {Name: "création translittérée", Method: "POST", Body: `{"action":" CREATE ","name":" Réseau NAS — ça ñ "}`}, + {Name: "création sans lettres", Method: "POST", Body: `{"action":"create","name":"!!!"}`}, + {Name: "création sans lettres bis", Method: "POST", Body: `{"action":"create","name":"???"}`}, + {Name: "création longue", Method: "POST", + Body: `{"action":"create","name":"` + strings.Repeat("Ab-", 30) + `"}`}, + {Name: "création très longue", Method: "POST", + Body: `{"action":"create","name":"` + strings.Repeat("ü", 70) + `"}`}, + {Name: "création vide", Method: "POST", Body: `{"action":"create","name":" "}`}, + {Name: "renommage", Method: "POST", Body: `{"action":"rename","slug":" alpha-2 ","name":" Bêta "}`}, + {Name: "renommage vide", Method: "POST", Body: `{"action":"rename","slug":"alpha-2","name":""}`}, + {Name: "renommage inconnu", Method: "POST", Body: `{"action":"rename","slug":"nope","name":"X"}`}, + {Name: "description", Method: "POST", Body: `{"action":"desc","slug":"alpha","desc":" Le projet Alpha. "}`}, + {Name: "description longue", Method: "POST", Body: `{"action":"desc","slug":"alpha-2","desc":"` + long + `"}`}, + {Name: "description coupée sur une espace", Method: "POST", + Body: `{"action":"desc","slug":"projet","desc":"` + strings.Repeat("é", 1999) + ` suite"}`}, + {Name: "description effacée", Method: "POST", Body: `{"action":"desc","slug":"alpha-2","desc":" "}`}, + {Name: "description inconnue", Method: "POST", Body: `{"action":"desc","slug":"nope","desc":"x"}`}, + {Name: "bascule", Method: "POST", Body: `{"action":"switch","slug":"alpha"}`}, + {Name: "bascule sur l'actif", Method: "POST", Body: `{"action":"switch","slug":"alpha"}`}, + {Name: "bascule inconnue", Method: "POST", Body: `{"action":"switch","slug":"nope"}`}, + {Name: "bascule pendant une génération", Generating: true, Method: "POST", Body: `{"action":"switch","slug":"generale"}`}, + {Name: "suppression pendant une génération", Generating: true, Method: "POST", Body: `{"action":"delete","slug":"alpha"}`}, + {Name: "suppression inconnue pendant une génération", Generating: true, Method: "POST", Body: `{"action":"delete","slug":"nope"}`}, + {Name: "bascule inconnue pendant une génération", Generating: true, Method: "POST", Body: `{"action":"switch","slug":"nope"}`}, + {Name: "suppression de l'actif", Method: "POST", Body: `{"action":"delete","slug":"alpha"}`}, + {Name: "suppression inconnue", Method: "POST", Body: `{"action":"delete","slug":"nope"}`}, + {Name: "suppression", Method: "POST", Body: `{"action":"delete","slug":"projet"}`}, + {Name: "bascule vers un projet sans discussion", Method: "POST", Body: `{"action":"switch","slug":"projet-2"}`}, + {Name: "suppression bis", Method: "POST", Body: `{"action":"delete","slug":"alpha-2"}`}, + {Name: "suppression ter", Method: "POST", Body: `{"action":"delete","slug":"reseau-nas-ca-n"}`}, + {Name: "liste après", Method: "GET"}, + {Name: "action inconnue", Method: "POST", Body: `{"action":"Nuke","slug":"generale"}`}, + {Name: "action vide", Method: "POST", Body: `{}`}, + {Name: "corps vide", Method: "POST", Body: ``}, + {Name: "corps illisible", Method: "POST", Body: `{"action":"create"`}, + {Name: "type faux", Method: "POST", Body: `{"action":"create","name":7}`}, + {Name: "méthode refusée", Method: "PUT", Body: `{}`}, + } { + conv.mu.Lock() + conv.Generating = st.Generating + conv.mu.Unlock() + rec := httptest.NewRecorder() + handleProjects(rec, httptest.NewRequest(st.Method, "/api/projects", strings.NewReader(st.Body))) + conv.mu.Lock() + conv.Generating = false + conv.mu.Unlock() + st.Status = rec.Code + var out any + if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { + t.Fatalf("%s : %q", st.Name, rec.Body.String()) + } + st.Out = parityZeroTimes(out) + st.After = snapshot() + steps = append(steps, st) + } + // Les dernières suppressions, jusqu'au dernier projet. + for _, p := range listProjects() { + if p.Slug == defaultProjectSlug { + continue + } + rec := httptest.NewRecorder() + handleProjects(rec, httptest.NewRequest("POST", "/api/projects", + strings.NewReader(`{"action":"delete","slug":"`+p.Slug+`"}`))) + var out any + _ = json.Unmarshal(rec.Body.Bytes(), &out) + steps = append(steps, parityProjectStep{Name: "suppression de " + p.Slug, Method: "POST", + Body: `{"action":"delete","slug":"` + p.Slug + `"}`, Status: rec.Code, Out: parityZeroTimes(out), After: snapshot()}) + } + rec := httptest.NewRecorder() + handleProjects(rec, httptest.NewRequest("POST", "/api/projects", strings.NewReader(`{"action":"delete","slug":"generale"}`))) + var out any + _ = json.Unmarshal(rec.Body.Bytes(), &out) + steps = append(steps, parityProjectStep{Name: "dernier projet", Method: "POST", + Body: `{"action":"delete","slug":"generale"}`, Status: rec.Code, Out: parityZeroTimes(out), After: snapshot()}) + + got, err := parityJSON(map[string]any{"seed": seed, "seed_store": seedStore, "seed_mem": seedMem, "steps": steps}) + if err != nil { + t.Fatal(err) + } + got = []byte(strings.ReplaceAll(string(got), home, "{{HOME}}")) + if os.Getenv("LOKI_PARITY_UPDATE") == "1" { + if err := os.MkdirAll(filepath.Dir(parityProjectsFile), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(parityProjectsFile, got, 0o644); err != nil { + t.Fatal(err) + } + return + } + want, err := os.ReadFile(parityProjectsFile) + 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("le hub des projets ne produit plus le journal rangé dans %s.\n"+ + "Si c'est voulu : LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteProjets,\n"+ + "puis cargo test --test projects_api dit si le port Rust suit.\n--- obtenu ---\n%s", parityProjectsFile, got) + } +} diff --git a/internal/loki/web_internal.go b/internal/loki/web_internal.go index 66d09cf..6d71694 100644 --- a/internal/loki/web_internal.go +++ b/internal/loki/web_internal.go @@ -125,6 +125,10 @@ func withInternalAPI(next http.Handler) http.Handler { handleInternalCDPShutdown(w, r) case "/internal/v1/service/action": handleInternalServiceAction(w, r) + case "/internal/v1/project/switch": + handleInternalProjectSwitch(w, r) + case "/internal/v1/project/drop-convs": + handleInternalProjectDropConvs(w, r) default: http.NotFound(w, r) } diff --git a/rust/loki-rs/src/api.rs b/rust/loki-rs/src/api.rs index 9b7866b..71e7c0e 100644 --- a/rust/loki-rs/src/api.rs +++ b/rust/loki-rs/src/api.rs @@ -46,6 +46,8 @@ pub struct Ctx { pub engine_ctl: Option>, /// Interrupteurs système tenus par la façade (`LOKI_SYSTEM=rust`). pub system: Option>, + /// Hub des projets tenu par la façade (`LOKI_PROJECTS=rust`). + pub projects: bool, /// Jeton de l'API interne : une requête qui le porte vient du Go. pub internal_token: String, } @@ -64,6 +66,7 @@ impl Ctx { || self.presets || self.engine_ctl.is_some() || self.system.is_some() + || self.projects } /// La requête vient du Go (jeton interne valide) : déjà authentifiée par diff --git a/rust/loki-rs/src/enginectl.rs b/rust/loki-rs/src/enginectl.rs index d112773..7bc9978 100644 --- a/rust/loki-rs/src/enginectl.rs +++ b/rust/loki-rs/src/enginectl.rs @@ -359,7 +359,7 @@ fn pid_alive(_: i32) -> bool { /// La conversation écrit-elle une réponse ? Celle de la façade si elle la /// tient, sinon celle du Go. -async fn generating(ctx: &Ctx) -> bool { +pub(crate) async fn generating(ctx: &Ctx) -> bool { if let Some(chat) = &ctx.chat { return chat.sessions.conv().is_generating(); } diff --git a/rust/loki-rs/src/lib.rs b/rust/loki-rs/src/lib.rs index 25bd3c4..ed8b0dc 100644 --- a/rust/loki-rs/src/lib.rs +++ b/rust/loki-rs/src/lib.rs @@ -30,6 +30,7 @@ pub mod oai; pub mod paths; pub mod presets; pub mod presets_api; +pub mod projects_api; pub mod proxy; pub mod push; pub mod serve; diff --git a/rust/loki-rs/src/main.rs b/rust/loki-rs/src/main.rs index 16074f8..78e1b8c 100644 --- a/rust/loki-rs/src/main.rs +++ b/rust/loki-rs/src/main.rs @@ -154,6 +154,8 @@ fn serve() -> ExitCode { // LOKI_SYSTEM=rust : les interrupteurs système (accès internet, // navigateur, clé de complétion, écoute réseau du moteur). let system_rust = held_by_facade("LOKI_SYSTEM"); + // LOKI_PROJECTS=rust : le hub des projets. + let projects_rust = held_by_facade("LOKI_PROJECTS"); let engine_rust = held_by_facade("LOKI_ENGINE") && match loki_rs::enginectl::usable( std::env::var("LOKI_CONTAINER").ok().as_deref(), @@ -182,6 +184,7 @@ fn serve() -> ExitCode { ("LOKI_PRESETS", presets_rust), ("LOKI_ENGINE", engine_rust), ("LOKI_SYSTEM", system_rust), + ("LOKI_PROJECTS", projects_rust), ]; for (var, on) in held { if on { @@ -285,6 +288,7 @@ fn serve() -> ExitCode { presets: presets_rust, engine_ctl, system: system_rust.then(|| std::sync::Arc::new(loki_rs::system::System::new())), + projects: projects_rust, }; let res = proxy::serve(listener, cfg, shutdown_signal()).await; eprintln!("[loki-rs] arrêt demandé — arrêt du serveur Go"); diff --git a/rust/loki-rs/src/memory.rs b/rust/loki-rs/src/memory.rs index 2b94a09..287617b 100644 --- a/rust/loki-rs/src/memory.rs +++ b/rust/loki-rs/src/memory.rs @@ -48,7 +48,7 @@ pub const MEM_INDEX_FILE: &str = "MEMORY.md"; /// Magic des contenus chiffrés (`memPageMagic`). pub const PAGE_MAGIC: &[u8] = b"LOKIMEMv1"; pub const BK_TRACKER: &str = "tracker"; -const ST_PROJECTS: &str = "projects"; +pub(crate) const ST_PROJECTS: &str = "projects"; const ST_ACTIVE_PROJECT: &str = "active_project"; const ST_AGENT: &str = "agent"; const DEFAULT_PROJECT_SLUG: &str = "generale"; @@ -400,7 +400,7 @@ pub fn parse_when(when: &str, now_ms: i64, tz: Tz) -> Result<(i64, bool), String )) } -fn now_ms() -> i64 { +pub(crate) fn now_ms() -> i64 { SystemTime::now() .duration_since(UNIX_EPOCH) .map_or(0, |d| d.as_millis() as i64) diff --git a/rust/loki-rs/src/projects_api.rs b/rust/loki-rs/src/projects_api.rs new file mode 100644 index 0000000..dee6559 --- /dev/null +++ b/rust/loki-rs/src/projects_api.rs @@ -0,0 +1,421 @@ +//! Hub des projets tenu par la façade (lot 18 de la migration, +//! `LOKI_PROJECTS=rust` ; expérimental, le Go reste le défaut) — port de +//! `handleProjects` / `projectDTOs` (`web_projects.go`) et des mutations de +//! `projects.go` : création (slug translittéré, unique), renommage, +//! description, suppression (discussions, trackers et mémoire du projet, actif +//! réassigné, dernier projet indestructible) et bascule. +//! +//! Le registre reste dans la base (`state/projects`), la mémoire de chaque +//! projet dans `memory/`. Le projet actif passe toujours par le Go +//! (`/internal/v1/project/activate`), qui en garde une copie en RAM. Les +//! discussions sont à qui tient la conversation : la façade les bascule et +//! les supprime elle-même quand elle la tient, sinon le Go +//! (`/internal/v1/project/switch`, `/internal/v1/project/drop-convs`). + +use std::path::Path; + +use http_body_util::{BodyExt, Limited}; +use hyper::body::Incoming; +use hyper::{Method, Request, Response, StatusCode}; +use serde_json::{Value, json}; + +use crate::api::Ctx; +use crate::auth::json as send_json; +use crate::chat::persist::{BK_CHAT, CK_INDEX, decode_index}; +use crate::gojson::{self, Kind}; +use crate::memory::{self, BK_TRACKER, Project, ST_PROJECTS, active_project, list_projects}; +use crate::paths::DEFAULT_PROJECT_SLUG; +use crate::proxy::Body; +use crate::store::{BK_STATE, Store}; + +/// Taille maximale d'un nom de projet, en runes. +pub const MAX_NAME: usize = 60; +/// Taille maximale d'une description, en runes. +pub const MAX_DESC: usize = 2000; + +/// Une erreur de route : (code, message). +type Fail = (StatusCode, String); + +fn bad(e: impl Into) -> Fail { + (StatusCode::BAD_REQUEST, e.into()) +} + +/// Les `n` premières runes. +fn runes(s: &str, n: usize) -> String { + s.chars().take(n).collect() +} + +/// `saveProjects` (`json.Marshal`, HTML échappé comme le Go). +async fn save_projects(store: &Store, list: &[Project]) -> Result<(), String> { + store + .put(BK_STATE, ST_PROJECTS, gojson::marshal(list).into_bytes()) + .await +} + +/// `projectMemoryDir`. +pub fn project_memory_dir(home: &Path, slug: &str) -> std::path::PathBuf { + let slug = if slug.is_empty() { + DEFAULT_PROJECT_SLUG + } else { + slug + }; + home.join("memory").join(slug) +} + +/// Pages d'un projet (`MemList` sans l'index) : fichiers `.md` visibles. +pub fn count_pages(dir: &Path) -> usize { + let Ok(rd) = std::fs::read_dir(dir) else { + return 0; + }; + rd.filter_map(|e| e.ok()) + .filter(|e| !e.file_type().is_ok_and(|t| t.is_dir())) + .map(|e| e.file_name().to_string_lossy().into_owned()) + .filter(|n| { + !n.starts_with('.') && n.to_lowercase().ends_with(".md") && !memory::is_index_file(n) + }) + .count() +} + +/// `slugifyIdent` puis le repli « projet » et le suffixe -2, -3… d'un slug +/// déjà pris. +pub fn unique_slug(name: &str, list: &[Project]) -> String { + let mut base = memory::slugify_ident(name); + if base.is_empty() { + base = "projet".into(); + } + let taken = |s: &str| list.iter().any(|p| p.slug == s); + let mut slug = base.clone(); + let mut i = 2; + while taken(&slug) { + slug = format!("{base}-{i}"); + i += 1; + } + slug +} + +/// `createProject`. +async fn create_project(store: &Store, home: &Path, name: &str) -> Result { + let name = name.trim(); + if name.is_empty() { + return Err("nom vide".into()); + } + let name = runes(name, MAX_NAME); + let mut list = list_projects(store).await; + let slug = unique_slug(&name, &list); + let dir = project_memory_dir(home, &slug); + std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; + let idx = dir.join(memory::MEM_INDEX_FILE); + if !idx.exists() { + let _ = write_private(&idx, memory::memory_seed(&name).as_bytes()); + } + let p = Project { + slug, + name, + created_at: memory::now_ms(), + desc: String::new(), + mem_mode: String::new(), + }; + list.push(p.clone()); + save_projects(store, &list).await?; + Ok(p) +} + +/// Fichier en 0600, comme `os.WriteFile(…, 0o600)`. +fn write_private(p: &Path, b: &[u8]) -> std::io::Result<()> { + use std::io::Write; + let mut o = std::fs::OpenOptions::new(); + o.write(true).create(true).truncate(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + o.mode(0o600); + } + o.open(p)?.write_all(b) +} + +/// `renameProject`. +async fn rename_project(store: &Store, slug: &str, name: &str) -> Result<(), String> { + let name = name.trim(); + if name.is_empty() { + return Err("nom vide".into()); + } + let name = runes(name, MAX_NAME); + let mut list = list_projects(store).await; + let Some(p) = list.iter_mut().find(|p| p.slug == slug) else { + return Err("projet introuvable".into()); + }; + p.name = name; + save_projects(store, &list).await +} + +/// `setProjectDesc` : tronquée à 2 000 runes (contexte injecté à chaque +/// conversation, pas un cahier des charges) ; vide = effacée. +async fn set_project_desc(store: &Store, slug: &str, desc: &str) -> Result<(), String> { + let mut desc = desc.trim().to_string(); + if desc.chars().count() > MAX_DESC { + desc = runes(&desc, MAX_DESC).trim().to_string(); + } + let mut list = list_projects(store).await; + let Some(p) = list.iter_mut().find(|p| p.slug == slug) else { + return Err("projet introuvable".into()); + }; + p.desc = desc; + save_projects(store, &list).await +} + +/// Le projet actif, par le Go (qui en garde une copie en RAM). +async fn activate(store: &Store, slug: &str) -> Result<(), Fail> { + let (code, v) = store + .internal_post("/internal/v1/project/activate", &json!({ "slug": slug })) + .await + .map_err(bad)?; + if code.is_success() { + return Ok(()); + } + Err(go_fail(code, &v)) +} + +/// Une réponse d'erreur du Go : 409 gardé, le reste en 400. +fn go_fail(code: StatusCode, v: &Value) -> Fail { + let msg = v + .get("error") + .and_then(Value::as_str) + .map(str::to_string) + .unwrap_or_else(|| code.to_string()); + let code = if code == StatusCode::CONFLICT { + StatusCode::CONFLICT + } else { + StatusCode::BAD_REQUEST + }; + (code, msg) +} + +fn refused(r: crate::chat::generate::Refused) -> Fail { + let code = if r.status == 409 { + StatusCode::CONFLICT + } else { + StatusCode::BAD_REQUEST + }; + (code, r.error) +} + +/// Bascule de la conversation sur un projet : la façade si elle la tient, +/// sinon le Go. +async fn switch_conversation(ctx: &Ctx, slug: &str) -> Result<(), Fail> { + if let Some(chat) = &ctx.chat { + return chat.project_switch(slug).await.map_err(refused); + } + let (code, v) = ctx + .store + .internal_post("/internal/v1/project/switch", &json!({ "slug": slug })) + .await + .map_err(bad)?; + if code.is_success() { + Ok(()) + } else { + Err(go_fail(code, &v)) + } +} + +/// `deleteProject`. +async fn delete_project(ctx: &Ctx, slug: &str) -> Result<(), Fail> { + let store = &ctx.store; + let list = list_projects(store).await; + if list.len() <= 1 { + return Err(bad("impossible de supprimer le dernier projet")); + } + let kept: Vec = list.iter().filter(|p| p.slug != slug).cloned().collect(); + if kept.len() == list.len() { + return Err(bad("projet introuvable")); + } + // Discussions du projet. Quand la façade tient la conversation, elle + // quitte d'abord le projet s'il est actif (sa discussion enregistrée, une + // du projet restant ouverte), puis supprime les autres ; sinon le Go. + if let Some(chat) = &ctx.chat { + if active_project(store).await == slug { + chat.project_switch(&kept[0].slug).await.map_err(refused)?; + } + chat.drop_project(slug).await.map_err(refused)?; + } else { + let (code, v) = store + .internal_post("/internal/v1/project/drop-convs", &json!({ "slug": slug })) + .await + .map_err(bad)?; + if !code.is_success() { + return Err(go_fail(code, &v)); + } + } + // Trackers du projet. + let prefix = format!("{slug}/"); + if let Ok(all) = store.all(BK_TRACKER).await { + for k in all.keys().filter(|k| k.starts_with(&prefix)) { + let _ = store.del(BK_TRACKER, k).await; + } + } + // Dossier disque du projet. + let _ = std::fs::remove_dir_all(project_memory_dir(&ctx.engine.home, slug)); + save_projects(store, &kept).await.map_err(bad)?; + if active_project(store).await == slug { + let _ = activate(store, &kept[0].slug).await; + } + Ok(()) +} + +/// `projectDTOs` : le registre et ses comptes, plus le projet actif. +async fn listing(ctx: &Ctx) -> Value { + let store = &ctx.store; + let active = active_project(store).await; + let mut convs: std::collections::HashMap = std::collections::HashMap::new(); + if let Ok(Some(b)) = store.get_open(BK_CHAT, CK_INDEX).await { + for m in decode_index(&b) { + let p = match m.project.trim() { + "" => DEFAULT_PROJECT_SLUG.to_string(), + s => s.to_string(), + }; + *convs.entry(p).or_default() += 1; + } + } + let trackers: Vec = store + .all(BK_TRACKER) + .await + .map(|m| m.into_keys().collect()) + .unwrap_or_default(); + let home = ctx.engine.home.clone(); + let list = list_projects(store).await; + let pages: Vec = { + let dirs: Vec<_> = list + .iter() + .map(|p| project_memory_dir(&home, &p.slug)) + .collect(); + tokio::task::spawn_blocking(move || dirs.iter().map(|d| count_pages(d)).collect()) + .await + .unwrap_or_default() + }; + let projects: Vec = list + .iter() + .enumerate() + .map(|(i, p)| { + let prefix = format!("{}/", p.slug); + let mut d = json!({ + "slug": p.slug, + "name": p.name, + "created": p.created_at, + "convs": convs.get(&p.slug).copied().unwrap_or(0), + "pages": pages.get(i).copied().unwrap_or(0), + "trackers": trackers.iter().filter(|k| k.starts_with(&prefix)).count(), + "active": p.slug == active, + }); + if !p.desc.is_empty() { + d["desc"] = json!(p.desc); + } + d + }) + .collect(); + json!({ "ok": true, "projects": projects, "active": active }) +} + +/// Les routes servies par ce module. +pub fn handles(path: &str) -> bool { + path == "/api/projects" +} + +const PROJECT_REQ_TYPE: &str = r#"struct { Action string "json:\"action\""; Slug string "json:\"slug\""; Name string "json:\"name\""; Desc string "json:\"desc\"" }"#; + +/// `from_go` : remise par le Go (tunnel), déjà authentifiée par lui. +pub async fn serve(ctx: &Ctx, req: Request, from_go: bool) -> Response { + let (parts, body) = req.into_parts(); + if !from_go && let Err(refus) = ctx.guard(&parts.headers).await { + return *refus; + } + match parts.method { + Method::GET => send_json(StatusCode::OK, &listing(ctx).await), + Method::POST => { + let raw = Limited::new(body, 1 << 20) + .collect() + .await + .map(|b| b.to_bytes().to_vec()) + .unwrap_or_default(); + let d = gojson::decode_struct( + &raw, + PROJECT_REQ_TYPE, + &[ + ("action", Kind::Str), + ("slug", Kind::Str), + ("name", Kind::Str), + ("desc", Kind::Str), + ], + ); + if let Some(e) = d.err.clone() { + return send_json(StatusCode::BAD_REQUEST, &json!({ "ok": false, "error": e })); + } + let slug = d.str("slug").trim().to_string(); + let store = &ctx.store; + let home = ctx.engine.home.clone(); + let action = d.str("action"); + let res: Result<(), Fail> = match action.trim().to_lowercase().as_str() { + "create" => create_project(store, &home, &d.str("name")) + .await + .map(|_| ()) + .map_err(bad), + "rename" => rename_project(store, &slug, &d.str("name")) + .await + .map_err(bad), + "desc" => set_project_desc(store, &slug, &d.str("desc")) + .await + .map_err(bad), + "delete" => { + // Une génération travaille dans la mémoire du projet actif : + // supprimer sous ses pieds la laisserait écrire dans le vide. + if crate::enginectl::generating(ctx).await { + Err((StatusCode::CONFLICT, "une génération est en cours".into())) + } else { + delete_project(ctx, &slug).await + } + } + "switch" => { + if crate::enginectl::generating(ctx).await { + Err((StatusCode::CONFLICT, "une génération est en cours".into())) + } else if ctx.chat.is_some() + && !list_projects(store).await.iter().any(|p| p.slug == slug) + { + Err(bad("projet introuvable")) + } else { + switch_conversation(ctx, &slug).await + } + } + _ => Err(bad(format!("action inconnue : {}", gojson::quote(&action)))), + }; + match res { + Ok(()) => send_json(StatusCode::OK, &listing(ctx).await), + Err((code, e)) => send_json(code, &json!({ "ok": false, "error": e })), + } + } + _ => send_json( + StatusCode::METHOD_NOT_ALLOWED, + &json!({ "ok": false, "error": "méthode non autorisée" }), + ), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn p(slug: &str) -> Project { + Project { + slug: slug.into(), + name: slug.into(), + created_at: 0, + desc: String::new(), + mem_mode: String::new(), + } + } + + #[test] + fn slugs_uniques() { + let list = vec![p("alpha"), p("alpha-2"), p("projet")]; + assert_eq!(unique_slug("Alpha", &list), "alpha-3"); + assert_eq!(unique_slug("!!!", &list), "projet-2"); + assert_eq!(unique_slug("Bêta", &list), "beta"); + } +} diff --git a/rust/loki-rs/src/proxy.rs b/rust/loki-rs/src/proxy.rs index 6e58743..99927ae 100644 --- a/rust/loki-rs/src/proxy.rs +++ b/rust/loki-rs/src/proxy.rs @@ -85,6 +85,8 @@ pub struct Config { /// Interrupteurs système (internet, navigateur, clé de complétion, /// réseau) tenus par la façade (`LOKI_SYSTEM=rust`) ; `None` : au Go. pub system: Option>, + /// Hub des projets tenu par la façade (`LOKI_PROJECTS=rust`). + pub projects: bool, } struct Proxy { @@ -121,6 +123,7 @@ pub async fn serve( presets: cfg.presets, engine_ctl: cfg.engine_ctl, system: cfg.system, + projects: cfg.projects, internal_token: cfg.internal_token.clone(), }, }); @@ -208,6 +211,9 @@ impl Proxy { { return chatapi::serve(&self.ctx, chat, req, from_go).await; } + if self.ctx.projects && crate::projects_api::handles(path) { + return crate::projects_api::serve(&self.ctx, req, from_go).await; + } if let Some(s) = &self.ctx.system && crate::system::handles(path) { diff --git a/rust/loki-rs/tests/common/fakego.rs b/rust/loki-rs/tests/common/fakego.rs index 2590362..71c89da 100644 --- a/rust/loki-rs/tests/common/fakego.rs +++ b/rust/loki-rs/tests/common/fakego.rs @@ -321,6 +321,59 @@ async fn handle(st: Arc, req: Request) -> Response st.set("state", "active_project", &slug); resp(204, "") } + // Conversation tenue par le Go (lot 18) : bascule (projectSwitch) et + // discussions d'un projet supprimé (convDelete), sur l'index en base. + "/internal/v1/project/switch" | "/internal/v1/project/drop-convs" => { + let body = req.into_body().collect().await.unwrap().to_bytes(); + let v: serde_json::Value = serde_json::from_slice(&body).unwrap_or_default(); + let slug = v["slug"].as_str().unwrap_or_default().trim().to_string(); + if *st.generating.lock().unwrap() { + return resp(409, r#"{"error":"une génération est en cours"}"#); + } + let mut idx: Vec = st + .get("chat", "index") + .and_then(|s| serde_json::from_str(&s).ok()) + .unwrap_or_default(); + let proj = |m: &serde_json::Value| match m["project"].as_str().unwrap_or("").trim() { + "" => "generale".to_string(), + s => s.to_string(), + }; + if path.ends_with("drop-convs") { + let gone: Vec = idx + .iter() + .filter(|m| proj(m) == slug) + .map(|m| m["id"].as_str().unwrap_or("").to_string()) + .collect(); + idx.retain(|m| proj(m) != slug); + st.set("chat", "index", &serde_json::to_string(&idx).unwrap()); + for id in &gone { + st.del("chat", &format!("conv:{id}")); + } + return resp( + 200, + serde_json::json!({ "dropped": gone.len() }).to_string(), + ); + } + let known = st + .get("state", "projects") + .and_then(|s| serde_json::from_str::>(&s).ok()) + .unwrap_or_default() + .iter() + .any(|p| p["slug"] == slug.as_str()); + if !known { + return resp(400, r#"{"error":"projet introuvable"}"#); + } + if st.get("state", "active_project").as_deref() == Some(slug.as_str()) { + return resp(204, ""); + } + st.set("state", "active_project", &slug); + if !idx.iter().any(|m| proj(m) == slug) { + let id = format!("c{}", idx.len() + 1_000_000); + idx.push(serde_json::json!({ "id": id, "title": "", "created": 0, "updated": 0, "turns": 0, "project": slug })); + st.set("chat", "index", &serde_json::to_string(&idx).unwrap()); + } + resp(204, "") + } "/internal/v1/turn/verifier" => { let body = req.into_body().collect().await.unwrap().to_bytes(); let v: serde_json::Value = serde_json::from_slice(&body).unwrap_or_default(); @@ -579,6 +632,7 @@ pub struct Opts { pub presets: bool, pub engine_ctl: Option>, pub system: Option>, + pub projects: bool, } /// Comme [`start`], avec la conversation tenue par la façade @@ -677,6 +731,7 @@ pub async fn start_with( presets: opts.presets, engine_ctl: opts.engine_ctl, system: opts.system, + projects: opts.projects, }; tokio::spawn(loki_rs::proxy::serve(fl, cfg, async move { let _ = rx.await; diff --git a/rust/loki-rs/tests/lot2.rs b/rust/loki-rs/tests/lot2.rs index a9f3c29..0a76390 100644 --- a/rust/loki-rs/tests/lot2.rs +++ b/rust/loki-rs/tests/lot2.rs @@ -103,6 +103,7 @@ async fn banc(cle: Option<&str>) -> Banc { presets: false, engine_ctl: None, system: None, + projects: false, }; tokio::spawn(loki_rs::proxy::serve(l, cfg, async move { let _ = rx.await; diff --git a/rust/loki-rs/tests/parity_projects/projects.json b/rust/loki-rs/tests/parity_projects/projects.json new file mode 100644 index 0000000..8db6b04 --- /dev/null +++ b/rust/loki-rs/tests/parity_projects/projects.json @@ -0,0 +1,4484 @@ +{ + "seed": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md" + ] + }, + "seed_mem": { + "memory/generale/.cache.md": "# .cache.md\n", + "memory/generale/MEMORY.md": "# Index mémoire — Générale\n\nUne ligne par page de ce projet (`- [titre](fichier.md) — accroche`). Tenu à jour automatiquement à la création et à la suppression d'une page ; l'accroche après le titre, elle, est libre.\n", + "memory/generale/a.md": "# a.md\n", + "memory/generale/b.MD": "# b.MD\n", + "memory/generale/notes.txt": "# notes.txt\n", + "memory/generale/sous/page.md": "# sous/page.md\n", + "memory/generale/z.md": "# z.md\n" + }, + "seed_store": { + "chat/conv:a1": "{\"id\":\"a1\",\"log\":[]}", + "chat/conv:a2": "{\"id\":\"a2\",\"log\":[]}", + "chat/conv:a3": "{\"id\":\"a3\",\"log\":[]}", + "chat/conv:g1": "{\"id\":\"g1\",\"log\":[]}", + "chat/conv:o1": "{\"id\":\"o1\",\"log\":[]}", + "chat/conv:x1": "{\"id\":\"x1\",\"log\":[]}", + "chat/index": "[{\"id\":\"g1\",\"title\":\"g1\",\"created\":1,\"updated\":10,\"turns\":0,\"project\":\"generale\"},{\"id\":\"o1\",\"title\":\"o1\",\"created\":2,\"updated\":20,\"turns\":0},{\"id\":\"a1\",\"title\":\"a1\",\"created\":3,\"updated\":30,\"turns\":0,\"project\":\"alpha\"},{\"id\":\"a2\",\"title\":\"a2\",\"created\":4,\"updated\":50,\"turns\":0,\"project\":\"alpha\"},{\"id\":\"a3\",\"title\":\"a3\",\"created\":5,\"updated\":40,\"turns\":0,\"project\":\"alpha\"},{\"id\":\"x1\",\"title\":\"x1\",\"created\":6,\"updated\":60,\"turns\":0,\"project\":\"ailleurs\"}]", + "state/active_project": "generale", + "state/projects": "[{\"slug\":\"generale\",\"name\":\"Générale\",\"created_at\":1}]", + "tracker/alpha/pas": "{\"name\":\"alpha/pas\",\"events\":[]}", + "tracker/alpha/sommeil": "{\"name\":\"alpha/sommeil\",\"events\":[]}", + "tracker/alphabet/x": "{\"name\":\"alphabet/x\",\"events\":[]}", + "tracker/generale/poids": "{\"name\":\"generale/poids\",\"events\":[]}" + }, + "steps": [ + { + "name": "liste", + "method": "GET", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md" + ] + } + }, + { + "name": "création", + "method": "POST", + "body": "{\"action\":\"create\",\"name\":\"Alpha\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md" + ] + } + }, + { + "name": "création, même nom", + "method": "POST", + "body": "{\"action\":\"create\",\"name\":\"Alpha\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha-2" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md" + ] + } + }, + { + "name": "création translittérée", + "method": "POST", + "body": "{\"action\":\" CREATE \",\"name\":\" Réseau NAS — ça ñ \"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md" + ] + } + }, + { + "name": "création sans lettres", + "method": "POST", + "body": "{\"action\":\"create\",\"name\":\"!!!\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md" + ] + } + }, + { + "name": "création sans lettres bis", + "method": "POST", + "body": "{\"action\":\"create\",\"name\":\"???\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md" + ] + } + }, + { + "name": "création longue", + "method": "POST", + "body": "{\"action\":\"create\",\"name\":\"Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md" + ] + } + }, + { + "name": "création très longue", + "method": "POST", + "body": "{\"action\":\"create\",\"name\":\"üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "création vide", + "method": "POST", + "body": "{\"action\":\"create\",\"name\":\" \"}", + "status": 400, + "out": { + "error": "nom vide", + "ok": false + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "renommage", + "method": "POST", + "body": "{\"action\":\"rename\",\"slug\":\" alpha-2 \",\"name\":\" Bêta \"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "renommage vide", + "method": "POST", + "body": "{\"action\":\"rename\",\"slug\":\"alpha-2\",\"name\":\"\"}", + "status": 400, + "out": { + "error": "nom vide", + "ok": false + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "renommage inconnu", + "method": "POST", + "body": "{\"action\":\"rename\",\"slug\":\"nope\",\"name\":\"X\"}", + "status": 400, + "out": { + "error": "projet introuvable", + "ok": false + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "description", + "method": "POST", + "body": "{\"action\":\"desc\",\"slug\":\"alpha\",\"desc\":\" Le projet Alpha. \"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "description longue", + "method": "POST", + "body": "{\"action\":\"desc\",\"slug\":\"alpha-2\",\"desc\":\"éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé fin\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "desc": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "desc": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "description coupée sur une espace", + "method": "POST", + "body": "{\"action\":\"desc\",\"slug\":\"projet\",\"desc\":\"ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé suite\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "desc": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "desc": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "description effacée", + "method": "POST", + "body": "{\"action\":\"desc\",\"slug\":\"alpha-2\",\"desc\":\" \"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 3, + "created": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "description inconnue", + "method": "POST", + "body": "{\"action\":\"desc\",\"slug\":\"nope\",\"desc\":\"x\"}", + "status": 400, + "out": { + "error": "projet introuvable", + "ok": false + }, + "after": { + "active": "generale", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "bascule", + "method": "POST", + "body": "{\"action\":\"switch\",\"slug\":\"alpha\"}", + "status": 200, + "out": { + "active": "alpha", + "ok": true, + "projects": [ + { + "active": false, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": true, + "convs": 3, + "created": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "alpha", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "bascule sur l'actif", + "method": "POST", + "body": "{\"action\":\"switch\",\"slug\":\"alpha\"}", + "status": 200, + "out": { + "active": "alpha", + "ok": true, + "projects": [ + { + "active": false, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": true, + "convs": 3, + "created": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "pages": 0, + "slug": "alpha", + "trackers": 2 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "alpha", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "bascule inconnue", + "method": "POST", + "body": "{\"action\":\"switch\",\"slug\":\"nope\"}", + "status": 400, + "out": { + "error": "projet introuvable", + "ok": false + }, + "after": { + "active": "alpha", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "bascule pendant une génération", + "generating": true, + "method": "POST", + "body": "{\"action\":\"switch\",\"slug\":\"generale\"}", + "status": 409, + "out": { + "error": "une génération est en cours", + "ok": false + }, + "after": { + "active": "alpha", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression pendant une génération", + "generating": true, + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"alpha\"}", + "status": 409, + "out": { + "error": "une génération est en cours", + "ok": false + }, + "after": { + "active": "alpha", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression inconnue pendant une génération", + "generating": true, + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"nope\"}", + "status": 409, + "out": { + "error": "une génération est en cours", + "ok": false + }, + "after": { + "active": "alpha", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "bascule inconnue pendant une génération", + "generating": true, + "method": "POST", + "body": "{\"action\":\"switch\",\"slug\":\"nope\"}", + "status": 409, + "out": { + "error": "une génération est en cours", + "ok": false + }, + "after": { + "active": "alpha", + "convs": [ + "a1:alpha", + "a2:alpha", + "a3:alpha", + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "desc": "Le projet Alpha.", + "name": "Alpha", + "slug": "alpha" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alpha/pas", + "alpha/sommeil", + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/alpha/", + "memory/alpha/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression de l'actif", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"alpha\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "pages": 0, + "slug": "projet", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression inconnue", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"nope\"}", + "status": 400, + "out": { + "error": "projet introuvable", + "ok": false + }, + "after": { + "active": "generale", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "desc": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "name": "!!!", + "slug": "projet" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/projet/", + "memory/projet/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"projet\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "bascule vers un projet sans discussion", + "method": "POST", + "body": "{\"action\":\"switch\",\"slug\":\"projet-2\"}", + "status": 200, + "out": { + "active": "projet-2", + "ok": true, + "projects": [ + { + "active": false, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Bêta", + "pages": 0, + "slug": "alpha-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": true, + "convs": 1, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Bêta", + "slug": "alpha-2" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/alpha-2/", + "memory/alpha-2/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression bis", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"alpha-2\"}", + "status": 200, + "out": { + "active": "projet-2", + "ok": true, + "projects": [ + { + "active": false, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Réseau NAS — ça ñ", + "pages": 0, + "slug": "reseau-nas-ca-n", + "trackers": 0 + }, + { + "active": true, + "convs": 1, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Réseau NAS — ça ñ", + "slug": "reseau-nas-ca-n" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/reseau-nas-ca-n/", + "memory/reseau-nas-ca-n/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression ter", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"reseau-nas-ca-n\"}", + "status": 200, + "out": { + "active": "projet-2", + "ok": true, + "projects": [ + { + "active": false, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": true, + "convs": 1, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "liste après", + "method": "GET", + "status": 200, + "out": { + "active": "projet-2", + "ok": true, + "projects": [ + { + "active": false, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": true, + "convs": 1, + "created": 0, + "name": "???", + "pages": 0, + "slug": "projet-2", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "action inconnue", + "method": "POST", + "body": "{\"action\":\"Nuke\",\"slug\":\"generale\"}", + "status": 400, + "out": { + "error": "action inconnue : \"Nuke\"", + "ok": false + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "action vide", + "method": "POST", + "body": "{}", + "status": 400, + "out": { + "error": "action inconnue : \"\"", + "ok": false + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "corps vide", + "method": "POST", + "status": 400, + "out": { + "error": "EOF", + "ok": false + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "corps illisible", + "method": "POST", + "body": "{\"action\":\"create\"", + "status": 400, + "out": { + "error": "unexpected EOF", + "ok": false + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "type faux", + "method": "POST", + "body": "{\"action\":\"create\",\"name\":7}", + "status": 400, + "out": { + "error": "json: cannot unmarshal number into Go struct field .name of type string", + "ok": false + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "méthode refusée", + "method": "PUT", + "body": "{}", + "status": 405, + "out": { + "error": "méthode non autorisée", + "ok": false + }, + "after": { + "active": "projet-2", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs", + "{{NEW}}:projet-2" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "???", + "slug": "projet-2" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/projet-2/", + "memory/projet-2/MEMORY.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression de projet-2", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"projet-2\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "pages": 0, + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "trackers": 0 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-Ab-", + "slug": "ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/", + "memory/ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a/MEMORY.md", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression de ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-ab-a\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + }, + { + "active": false, + "convs": 0, + "created": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "pages": 0, + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "trackers": 0 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + }, + { + "created_at": 0, + "name": "üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü", + "slug": "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/", + "memory/uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu/MEMORY.md" + ] + } + }, + { + "name": "suppression de uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu\"}", + "status": 200, + "out": { + "active": "generale", + "ok": true, + "projects": [ + { + "active": true, + "convs": 2, + "created": 0, + "name": "Générale", + "pages": 3, + "slug": "generale", + "trackers": 1 + } + ] + }, + "after": { + "active": "generale", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md" + ] + } + }, + { + "name": "dernier projet", + "method": "POST", + "body": "{\"action\":\"delete\",\"slug\":\"generale\"}", + "status": 400, + "out": { + "error": "impossible de supprimer le dernier projet", + "ok": false + }, + "after": { + "active": "generale", + "convs": [ + "g1:generale", + "o1:", + "x1:ailleurs" + ], + "projects": [ + { + "created_at": 0, + "name": "Générale", + "slug": "generale" + } + ], + "trackers": [ + "alphabet/x", + "generale/poids" + ], + "tree": [ + "memory/", + "memory/generale/", + "memory/generale/.cache.md", + "memory/generale/MEMORY.md", + "memory/generale/a.md", + "memory/generale/b.MD", + "memory/generale/notes.txt", + "memory/generale/sous/", + "memory/generale/sous/page.md", + "memory/generale/z.md" + ] + } + } + ] +} diff --git a/rust/loki-rs/tests/projects_api.rs b/rust/loki-rs/tests/projects_api.rs new file mode 100644 index 0000000..5e1f5a1 --- /dev/null +++ b/rust/loki-rs/tests/projects_api.rs @@ -0,0 +1,223 @@ +//! Lot 18 : hub des projets tenu par la façade (`LOKI_PROJECTS=rust`). +//! +//! Le journal tiré du Go (`internal/loki/parity_projects_test.go` → +//! `tests/parity_projects/projects.json`) est rejoué par la façade, la +//! conversation tenue par le (faux) Go : liste et comptes, création, +//! renommage, description, bascule, suppression, refus et erreurs, avec après +//! chaque appel le registre, le projet actif, l'arbre memory/, les trackers et +//! l'index des discussions. La bascule et la suppression quand la façade tient +//! la conversation sont vérifiées de bout en bout. + +mod common; + +use std::collections::BTreeSet; +use std::path::Path; + +use common::fakego::{self, CryptMode}; +use serde_json::{Value, json}; + +fn fixture() -> Value { + serde_json::from_str(include_str!("parity_projects/projects.json")).expect("journal") +} + +fn s(v: &Value) -> &str { + v.as_str().unwrap() +} + +/// Les dates propres à chaque exécution, à zéro (comme le journal). +fn zero_times(v: &mut Value) { + match v { + Value::Object(o) => { + for (k, x) in o.iter_mut() { + if matches!(k.as_str(), "created" | "created_at" | "updated") { + *x = json!(0); + } else { + zero_times(x); + } + } + } + Value::Array(a) => a.iter_mut().for_each(zero_times), + _ => {} + } +} + +fn tree(home: &Path) -> Vec { + fn walk(root: &Path, p: &Path, out: &mut Vec) { + let rel = p.strip_prefix(root).unwrap().to_string_lossy().into_owned(); + if p.is_dir() { + out.push(format!("{rel}/")); + for e in std::fs::read_dir(p).unwrap().flatten() { + walk(root, &e.path(), out); + } + } else { + out.push(rel); + } + } + let mut out = Vec::new(); + if home.join("memory").exists() { + walk(home, &home.join("memory"), &mut out); + } + out.sort(); + out +} + +fn snapshot(go: &fakego::FakeGo, home: &Path, seen: &BTreeSet) -> Value { + let mut reg: Value = go + .get("state", "projects") + .and_then(|s| serde_json::from_str(&s).ok()) + .unwrap_or(Value::Null); + zero_times(&mut reg); + let st = go.store.lock().unwrap(); + let mut trackers: Vec = st + .get("tracker") + .map(|b| b.keys().cloned().collect()) + .unwrap_or_default(); + trackers.sort(); + drop(st); + let idx: Vec = go + .get("chat", "index") + .and_then(|s| serde_json::from_str(&s).ok()) + .unwrap_or_default(); + let mut convs: Vec = idx + .iter() + .map(|m| { + let id = s(&m["id"]); + let id = if seen.contains(id) { id } else { "{{NEW}}" }; + format!("{id}:{}", m["project"].as_str().unwrap_or("")) + }) + .collect(); + convs.sort(); + json!({ + "projects": reg, + "active": go.get("state", "active_project").unwrap_or_default(), + "tree": tree(home), + "trackers": trackers, + "convs": convs, + }) +} + +#[tokio::test] +async fn parite_du_hub() { + let fx = fixture(); + let tmp = tempfile::tempdir().unwrap(); + let home = tmp.path().to_path_buf(); + for (rel, body) in fx["seed_mem"].as_object().unwrap() { + let p = home.join(rel); + std::fs::create_dir_all(p.parent().unwrap()).unwrap(); + std::fs::write(p, s(body)).unwrap(); + } + // Le dossier vide de la graine (sous/ a une page : rien d'autre à créer). + let (facade, go, _stop, _) = fakego::start_with( + home.clone(), + fakego::Opts { + projects: true, + ..Default::default() + }, + ) + .await; + *go.crypt_mode.lock().unwrap() = CryptMode::Plain; + for (k, v) in fx["seed_store"].as_object().unwrap() { + let (b, key) = k.split_once('/').unwrap(); + go.set(b, key, s(v)); + } + let seen: BTreeSet = + serde_json::from_str::>(s(&fx["seed_store"]["chat/index"])) + .unwrap() + .iter() + .map(|m| s(&m["id"]).to_string()) + .collect(); + assert_eq!(snapshot(&go, &home, &seen), fx["seed"], "graine"); + for st in fx["steps"].as_array().unwrap() { + let name = s(&st["name"]); + *go.generating.lock().unwrap() = st["generating"].as_bool() == Some(true); + let (code, body) = fakego::send( + facade, + s(&st["method"]), + "/api/projects", + &[], + st["body"].as_str().unwrap_or(""), + ) + .await; + *go.generating.lock().unwrap() = false; + assert_eq!( + code.as_u16() as i64, + st["status"].as_i64().unwrap(), + "{name}" + ); + let mut out: Value = serde_json::from_slice(&body).unwrap(); + zero_times(&mut out); + assert_eq!(out, st["out"], "{name}"); + assert_eq!(snapshot(&go, &home, &seen), st["after"], "{name} : état"); + } +} + +#[tokio::test] +async fn bascule_et_suppression_par_la_facade() { + let tmp = tempfile::tempdir().unwrap(); + let home = tmp.path().to_path_buf(); + let (facade, go, _stop, chat) = fakego::start_with( + home.clone(), + fakego::Opts { + chat: true, + projects: true, + ..Default::default() + }, + ) + .await; + let chat = chat.unwrap(); + let post = |body: &'static str| async move { + let (c, b) = fakego::send(facade, "POST", "/api/projects", &[], body).await; + (c.as_u16(), serde_json::from_slice::(&b).unwrap()) + }; + let (c, _) = post(r#"{"action":"create","name":"Alpha"}"#).await; + assert_eq!(c, 200); + let (c, v) = post(r#"{"action":"switch","slug":"alpha"}"#).await; + assert_eq!((c, v["active"].clone()), (200, json!("alpha"))); + // La conversation de la façade a suivi : une discussion d'Alpha ouverte. + let idx = chat.sessions.read_index().await; + let open = chat.sessions.conv().id(); + assert!( + idx.iter().any(|m| m.id == open && m.project == "alpha"), + "{idx:?}" + ); + assert!( + go.conv_log + .lock() + .unwrap() + .contains(&"project alpha".to_string()) + ); + // Projet introuvable : refusé par la façade, sans toucher à la conversation. + let (c, v) = post(r#"{"action":"switch","slug":"nope"}"#).await; + assert_eq!((c, v["error"].clone()), (400, json!("projet introuvable"))); + // Suppression du projet actif : la façade revient sur Générale et supprime + // les discussions d'Alpha ; le Go n'y touche pas. + let (c, v) = post(r#"{"action":"delete","slug":"alpha"}"#).await; + assert_eq!((c, v["active"].clone()), (200, json!("generale")), "{v}"); + let idx = chat.sessions.read_index().await; + assert!(idx.iter().all(|m| m.project != "alpha"), "{idx:?}"); + assert!(!home.join("memory/alpha").exists()); + // Pendant un tour de la façade : 409. + let (c, _) = post(r#"{"action":"create","name":"Beta"}"#).await; + assert_eq!(c, 200); + let ep = chat.sessions.conv().begin_turn(Box::new(|| {})).unwrap(); + let (c, v) = post(r#"{"action":"delete","slug":"beta"}"#).await; + assert_eq!( + (c, v["error"].clone()), + (409, json!("une génération est en cours")) + ); + let (c, _) = post(r#"{"action":"switch","slug":"beta"}"#).await; + assert_eq!(c, 409); + // Le tour passe avant tout le reste, projet inconnu compris (comme quand + // le Go tient la conversation). + let (c, _) = post(r#"{"action":"switch","slug":"nope"}"#).await; + assert_eq!(c, 409); + chat.sessions.conv().release_turn(ep); +} + +#[tokio::test] +async fn sans_la_variable_le_hub_reste_au_go() { + let tmp = tempfile::tempdir().unwrap(); + let (facade, _go, _stop) = fakego::start(tmp.path().to_path_buf()).await; + let (_, _, b) = fakego::get(facade, "/api/projects", &[]).await; + assert_eq!(&b[..], b"go:/api/projects"); +} diff --git a/rust/loki-rs/tests/proxy.rs b/rust/loki-rs/tests/proxy.rs index 2253c6b..175e7a2 100644 --- a/rust/loki-rs/tests/proxy.rs +++ b/rust/loki-rs/tests/proxy.rs @@ -109,6 +109,7 @@ async fn facade(backend: SocketAddr) -> (SocketAddr, oneshot::Sender<()>) { presets: false, engine_ctl: None, system: None, + projects: false, }; tokio::spawn(loki_rs::proxy::serve(l, cfg, async move { let _ = rx.await;