// SPDX-License-Identifier: GPL-3.0-or-later package plan import ( "encoding/json" "strings" "testing" "time" ) func TestJSONDir(t *testing.T) { chains := []Chain{{ File: file("/r", "x.pdf"), Steps: []Step{ {Kind: Copy, Rule: "b", Src: "/r/x.pdf", Dst: "/backup/x.pdf", Reason: `content "acme ltd"`}, {Kind: Move, Rule: "a", Src: "/r/x.pdf", Dst: "/r/W/x.pdf", Displaces: "/r/W/x.pdf"}, {Kind: Rename, Rule: "r", Src: "/r/W/x.pdf", Dst: "/r/W/2026-x.pdf"}, {Kind: Trash, Rule: "t", Src: "/r/W/2026-x.pdf"}, {Kind: DeletePermanent, Rule: "z", Src: "/r/W/x.pdf", Skip: "deleted by rule a"}, }, Warnings: []string{"moved more than once; a (stop) is probably missing"}, }} b, err := json.MarshalIndent(JSON{Version: 1, Note: jsonNote, Dirs: []JSONDir{NewJSONDir("dl", "/r", chains, nil, nil)}}, "", " ") if err != nil { t.Fatal(err) } out := string(b) for _, want := range []string{ `"version": 1`, `"note": "the shape of this document is unstable before krino 1.0"`, `"name": "dl"`, `"rel": "x.pdf"`, `"action": "copy"`, // D14: the reason a rule matched is carried into the JSON document too. `"reason": "content \"acme ltd\""`, // D7: rename and trash were previously covered only by inspection, so // an edit garbling either name would have passed silently. `"action": "rename"`, `"action": "trash"`, `"action": "move"`, `"displaces": "/r/W/x.pdf"`, `"action": "delete"`, `"skip": "deleted by rule a"`, } { if !strings.Contains(out, want) { t.Errorf("json lacks %s:\n%s", want, out) } } if strings.Contains(out, `"dst": ""`) { t.Error("empty dst must be omitted") } var round JSON if err := json.Unmarshal(b, &round); err != nil { t.Fatalf("does not round-trip: %v", err) } if !round.Dirs[0].Files[0].ModTime.Equal(time.Date(2026, 8, 15, 12, 0, 0, 0, time.UTC)) { t.Errorf("mtime did not survive: %v", round.Dirs[0].Files[0].ModTime) } } func TestJSONDirEmptyStepsAndFilesAreArraysNotNull(t *testing.T) { stepless := []Chain{{File: file("/r", "y.pdf")}} dir := NewJSONDir("dl", "/r", stepless, nil, nil) b, err := json.Marshal(dir) if err != nil { t.Fatal(err) } if strings.Contains(string(b), `"steps":null`) { t.Errorf("stepless file's steps must be [], not null: %s", b) } if !strings.Contains(string(b), `"steps":[]`) { t.Errorf("stepless file's steps should marshal as []: %s", b) } empty := NewJSONDir("dl", "/r", nil, nil, nil) b, err = json.Marshal(empty) if err != nil { t.Fatal(err) } if strings.Contains(string(b), `"files":null`) { t.Errorf("empty dir's files must be [], not null: %s", b) } if !strings.Contains(string(b), `"files":[]`) { t.Errorf("empty dir's files should marshal as []: %s", b) } }