From 83bba8d20b6ab77869984181e67daa84c3eae8ca Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Nov 2026 21:52:00 +0100 Subject: [PATCH] test(import): a hand-edited backup cannot raise the role or reach other accounts Co-Authored-By: Claude Opus 5.5 --- internal/storage/import_markdown_test.go | 50 ++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/internal/storage/import_markdown_test.go b/internal/storage/import_markdown_test.go index dd6ada6..faf51cd 100644 --- a/internal/storage/import_markdown_test.go +++ b/internal/storage/import_markdown_test.go @@ -125,6 +125,56 @@ func TestImportMindSiftJSONRoundTrip(t *testing.T) { } } +// A backup changed by hand cannot raise the importer's role or reach other accounts: the profile in it +// (role, id) is ignored, and notes and blocks get new IDs and no task links. +func TestImportIgnoresProfileAndForeignIDs(t *testing.T) { + store, anna, ben := newImportStore(t) + annaDoc, _ := store.CreateDocument(anna.ID, "", "Annas Notiz", "", "", []domain.DocumentBlock{{ID: "blk_anna", Type: "bullet", Content: "Original"}}, nil, false) + annaTask, _ := store.CreateThoughtWithDetails(anna.ID, "Annas Aufgabe", "normal", "manual", "", "", "todo", "") + + exp := domain.FullExport{ + ExportedAt: "2026-11-02T00:00:00Z", + Version: "manipuliert", + User: domain.User{ID: anna.ID, Email: anna.Email, Role: "admin", IsActive: true}, + Documents: []domain.Document{{ + ID: annaDoc.ID, + Title: "Übernommen?", + Blocks: []domain.DocumentBlock{ + {ID: "blk_anna", Type: "bullet", Content: "Überschrieben?"}, + {ID: "blk_x", Type: "todo", Content: "Verknüpft?", TaskID: annaTask.ID}, + }, + }}, + } + data, _ := json.Marshal(exp) + plan, err := PlanMindSiftJSONImport(data) + if err != nil { + t.Fatalf("plan: %v", err) + } + if _, err := store.ApplyImport(ben.ID, plan); err != nil { + t.Fatalf("apply: %v", err) + } + + if u, _ := store.GetUserByID(ben.ID); u.Role != "member" { + t.Errorf("import must not change the role, got %q", u.Role) + } + if u, _ := store.GetUserByID(anna.ID); u.Role != anna.Role { + t.Errorf("import must not touch the account named in the backup, role now %q", u.Role) + } + if d, err := store.GetDocumentByID(anna.ID, annaDoc.ID); err != nil || d.Title != "Annas Notiz" || d.Blocks[0].Content != "Original" { + t.Errorf("Anna's note must stay as it was: %+v %v", d, err) + } + docs, _ := store.GetDocuments(ben.ID) + if len(docs) != 1 || docs[0].ID == annaDoc.ID || docs[0].Title != "Übernommen?" { + t.Fatalf("the note should be Ben's own copy with a new ID: %+v", docs) + } + got, _ := store.GetDocumentByID(ben.ID, docs[0].ID) + for _, b := range got.Blocks { + if b.ID == "blk_anna" || b.ID == "blk_x" || b.TaskID != "" { + t.Errorf("imported blocks need new IDs and no task links: %+v", b) + } + } +} + func zipOf(t *testing.T, files map[string]string) []byte { t.Helper() var buf bytes.Buffer