1package snapshotcodec
2
3import (
4 "bytes"
5 "encoding/hex"
6 "testing"
7
8 vt "github.com/bnema/vev-vt"
9 "github.com/bnema/vev/internal/domain/layout"
10)
11
12const compactVEVSGolden = "564556530005000000000104dbb0765f0007726573746f7265000000000000007b0000000100037461620050001800000000000000020001700000017000010001700006737461626c6500042f746d70000000010000003f5654433100000001000000000000002a0000000200000001000000010000000000000000000000290000000100000000410000000000000000200000000000000000105654433100000000000000000000002a000000645654433100000001000000000000002b000000020000000100000002000000000900000000000000000000ffffffffffffffff010203000000000000000000000000000000000000000000002a00000002000000754c000000010000000000000000010100"
13
14func TestVEVSV4CompactGoldenRestore(t *testing.T) {
15 const golden = compactVEVSGolden
16 data, err := hex.DecodeString(golden)
17 if err != nil {
18 t.Fatal(err)
19 }
20
21 got, err := Unmarshal(data)
22 if err != nil {
23 t.Fatalf("Unmarshal golden VEVS: %v", err)
24 }
25 if got.Name != "restore" || got.CreatedAt != 123 || len(got.Tabs) != 1 {
26 t.Fatalf("session header = %#v", got)
27 }
28 tab := got.Tabs[0]
29 if tab.StableID != "tab" || tab.Cols != 80 || tab.Rows != 24 || tab.NextPaneID != 2 || tab.Focus != layout.PaneID("p") || len(tab.Panes) != 1 {
30 t.Fatalf("tab header = %#v", tab)
31 }
32 pane := tab.Panes[0]
33 if pane.ID != layout.PaneID("p") || pane.StableID != "stable" || pane.Cwd != "/tmp" || pane.Process != nil || len(pane.SealedChunks) != 1 {
34 t.Fatalf("pane header = %#v", pane)
35 }
36
37 sealed, err := vt.UnmarshalHistory(pane.SealedChunks[0])
38 if err != nil {
39 t.Fatalf("sealed history: %v", err)
40 }
41 if sealed.RowID(0) != 41 || sealed.Row(0)[0].Rune != 'A' {
42 t.Fatalf("sealed history = row %d %#v", sealed.RowID(0), sealed.Row(0))
43 }
44 tail, err := vt.UnmarshalHistory(pane.Tail)
45 if err != nil {
46 t.Fatalf("history tail: %v", err)
47 }
48 if tail.Len() != 0 || tail.NextRowID() != 42 {
49 t.Fatalf("history tail = len %d next %d", tail.Len(), tail.NextRowID())
50 }
51 transcript, err := vt.UnmarshalHistory(pane.Transcript)
52 if err != nil {
53 t.Fatalf("recovery transcript: %v", err)
54 }
55 if transcript.RowID(0) != 42 || transcript.Row(0)[0].Rune != '界' {
56 t.Fatalf("recovery transcript = row %d %#v", transcript.RowID(0), transcript.Row(0))
57 }
58
59 screen, err := vt.NewScreenWithRecoveryTranscript(8, 2, vt.HistoryConfig{MaxRows: 8, ChunkRows: 2}, pane.SealedChunks, pane.Tail, pane.Transcript)
60 if err != nil {
61 t.Fatalf("restore VT history: %v", err)
62 }
63 view := screen.History().View()
64 if view.Len() != 2 || view.RowID(0) != 41 || view.RowID(1) != 42 || view.NextRowID() != 43 {
65 t.Fatalf("restored history = len %d IDs %d/%d next %d", view.Len(), view.RowID(0), view.RowID(1), view.NextRowID())
66 }
67}
68
69func TestVEVSV4GoldenRejectsMalformedInput(t *testing.T) {
70 const golden = compactVEVSGolden
71 valid, err := hex.DecodeString(golden)
72 if err != nil {
73 t.Fatal(err)
74 }
75 mutate := func(fn func([]byte)) []byte {
76 out := append([]byte(nil), valid...)
77 fn(out)
78 return out
79 }
80 cases := []struct {
81 name string
82 data []byte
83 }{
84 {name: "truncated", data: valid[:len(valid)-1]},
85 {name: "trailing bytes", data: append(append([]byte(nil), valid...), 0)},
86 {name: "bad magic", data: mutate(func(data []byte) { data[0] ^= 0xff })},
87 {name: "bad version", data: mutate(func(data []byte) { data[4] = 3 })},
88 {name: "bad flags", data: mutate(func(data []byte) { data[7] = 1 })},
89 {name: "bad crc", data: mutate(func(data []byte) { data[15] ^= 0xff })},
90 }
91
92 body := append([]byte(nil), valid[16:]...)
93 if offset := bytes.Index(body, []byte("VTC1")); offset < 0 {
94 t.Fatal("golden has no VT blob")
95 } else {
96 body[offset+3] = '2'
97 cases = append(cases, struct {
98 name string
99 data []byte
100 }{name: "bad nested VT version", data: v4Envelope(version, 0, body)})
101 }
102
103 for _, test := range cases {
104 t.Run(test.name, func(t *testing.T) {
105 reject(t, test.data, nil)
106 })
107 }
108 for length := 0; length < len(valid); length++ {
109 reject(t, valid[:length], nil)
110 }
111}