diff --git a/internal/export/export_test.go b/internal/export/export_test.go new file mode 100644 index 0000000..36a33a1 --- /dev/null +++ b/internal/export/export_test.go @@ -0,0 +1,74 @@ +package export + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + "github.com/andydixon/mapsize/internal/inventory" +) + +const hostile = "evil\x1b]0;pwned\x07\x1b[2J" + +func tree() *inventory.Tree { + t := inventory.New("/r", inventory.KindDir) + d := t.Add(0, "d", inventory.KindDir) + f := t.Add(d, hostile, inventory.KindFile) + t.Node(f).Size, t.Node(f).Alloc = 10, 4096 + t.Recompute() + t.Stats.Complete = true + return t +} + +func noControl(t *testing.T, s string) { + t.Helper() + if strings.ContainsAny(s, "\x1b\x07") { + t.Fatalf("raw control characters in output: %q", s) + } +} + +func TestOutputsAreTerminalSafe(t *testing.T) { + tr := tree() + var b bytes.Buffer + if err := CSV(&b, tr, 0, -1); err != nil { + t.Fatal(err) + } + noControl(t, b.String()) + if !strings.Contains(b.String(), `evil\x1b]0;pwned\x07`) { + t.Fatalf("CSV should show the escaped name: %s", b.String()) + } + + b.Reset() + Table(&b, tr, []inventory.NodeID{2}, inventory.SizeAllocated) + Summary(&b, tr) + noControl(t, b.String()) + + b.Reset() + if err := JSON(&b, tr, 0, -1); err != nil { + t.Fatal(err) + } + noControl(t, b.String()) + var v struct { + Tree struct { + Children []struct { + Children []struct{ Name string } + } + } + } + if err := json.Unmarshal(b.Bytes(), &v); err != nil { + t.Fatal(err) + } + // JSON keeps the exact name (escaped by the encoder, decoded back). + if got := v.Tree.Children[0].Children[0].Name; got != hostile { + t.Fatalf("JSON name %q", got) + } +} + +func TestJSONDepthLimit(t *testing.T) { + var b bytes.Buffer + JSON(&b, tree(), 0, 1) + if strings.Contains(b.String(), "evil") { + t.Fatal("depth 1 should not include grandchildren") + } +}