-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdiff_test.go
More file actions
163 lines (146 loc) · 5.19 KB
/
Copy pathdiff_test.go
File metadata and controls
163 lines (146 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package dyfields
import (
"encoding/json"
"reflect"
"testing"
)
func diffSchema(t *testing.T) *Compiled {
t.Helper()
c, err := Load([]byte(`{
"schema_version": 1,
"groups": [{
"key": "customer",
"label": "Customer",
"fields": [
{"name": "name", "type": "string", "label": "Name"},
{"name": "note", "type": "string", "label": "Note"},
{"name": "tags", "type": "list", "label": "Tags", "items": {"type": "string"}},
{"name": "national_id", "type": "string", "label": "ID", "mask": {"keep_tail": 4}},
{"name": "contacts", "type": "object_list", "label": "Contacts", "fields": [
{"name": "label", "type": "string", "label": "Label"},
{"name": "phone", "type": "string", "label": "Phone", "mask": {"keep_tail": 4}},
{"name": "token", "type": "secret", "label": "Token"}
]}
]
}]
}`))
if err != nil {
t.Fatalf("schema does not compile: %v", err)
}
return c
}
func diffDoc() ValueDocument {
return ValueDocument{
Values: map[string]any{
"name": "Wu Mei-ling",
"note": "vip",
"tags": []any{"a", "b"},
"national_id": "A123456789",
"contacts": []any{
map[string]any{"$id": "c1", "label": "mobile", "phone": "0912345678"},
map[string]any{"$id": "c2", "label": "home", "phone": "0223456789"},
},
},
Secrets: map[string]string{"contacts.c1.token": "t-1"},
}
}
// The whole point: a form that loaded a redacted document and posted it back
// unchanged has nothing to say.
func TestDiffOfAnUntouchedRedactedDocumentIsEmpty(t *testing.T) {
c := diffSchema(t)
stored, errs := c.Validate(diffDoc())
if len(errs) != 0 {
t.Fatalf("fixture does not validate: %v", errs)
}
safe := c.Redact(stored)
loaded := ValueDocument{Values: safe.Values, Secrets: map[string]string{}}
p := c.Diff(loaded, loaded.Clone())
if len(p.Values) != 0 || len(p.Secrets) != 0 {
t.Fatalf("an untouched form produced a patch: %+v", p)
}
}
// And when one field is edited, that field is all that travels -- the masked
// ones the user never touched stay behind.
func TestDiffSendsOnlyWhatChanged(t *testing.T) {
c := diffSchema(t)
stored, _ := c.Validate(diffDoc())
safe := c.Redact(stored)
loaded := ValueDocument{Values: safe.Values, Secrets: map[string]string{}}
edited := loaded.Clone()
edited.Values["name"] = "Wu Mei-ling (updated)"
p := c.Diff(loaded, edited)
want := map[string]any{"name": "Wu Mei-ling (updated)"}
if !reflect.DeepEqual(p.Values, want) {
t.Fatalf("patch carries more than the edit: %+v", p.Values)
}
got, errs := c.Apply(stored, p)
if len(errs) != 0 {
t.Fatalf("applying the patch failed: %v", errs)
}
if got.Values["national_id"] != "A123456789" {
t.Fatalf("the untouched masked value did not survive: %v", got.Values["national_id"])
}
if got.Secrets["contacts.c1.token"] != "t-1" {
t.Fatalf("the untouched secret did not survive: %v", got.Secrets)
}
}
// Apply(base, Diff(base, next)) == next is the property that makes Diff usable
// without reading it: whatever the user did to the form, the server ends up
// with the document the user is looking at.
func TestDiffRoundTrips(t *testing.T) {
c := diffSchema(t)
base, _ := c.Validate(diffDoc())
next := base.Clone()
delete(next.Values, "note") // cleared
next.Values["tags"] = []any{"a", "c", "d"} // replaced
next.Values["national_id"] = "B987654321" // really edited
contacts := next.Values["contacts"].([]any) //
contacts[0].(map[string]any)["label"] = "work" // one row edited
next.Values["contacts"] = []any{ //
contacts[0], //
map[string]any{"$id": "c3", "label": "office"}, // one row added
} // and c2 removed
next.Secrets["contacts.c3.token"] = "t-3"
p := c.Diff(base, next)
got, errs := c.Apply(base, p)
if len(errs) != 0 {
t.Fatalf("the round trip does not validate: %v", errs)
}
want, _ := c.Validate(next)
if !reflect.DeepEqual(got.Values, want.Values) {
g, _ := json.Marshal(got.Values)
w, _ := json.Marshal(want.Values)
t.Fatalf("round trip lost something\n got: %s\nwant: %s", g, w)
}
if !reflect.DeepEqual(got.Secrets, want.Secrets) {
t.Fatalf("round trip lost a secret\n got: %v\nwant: %v", got.Secrets, want.Secrets)
}
}
// A row the user deleted has to be said out loud, because an absent entry
// means "unchanged" on the merge side.
func TestDiffMarksDeletedEntries(t *testing.T) {
c := diffSchema(t)
base, _ := c.Validate(diffDoc())
next := base.Clone()
next.Values["contacts"] = []any{next.Values["contacts"].([]any)[0]}
p := c.Diff(base, next)
entries, ok := p.Values["contacts"].([]any)
if !ok || len(entries) != 1 {
t.Fatalf("expected exactly the deletion: %+v", p.Values["contacts"])
}
e := entries[0].(map[string]any)
if e[KeyItemID] != "c2" || e[KeyDeleted] != true {
t.Fatalf("the deleted row is not marked: %v", e)
}
}
// A secret is never read back into a form, so an absent one cannot be told
// from a deleted one. Diff keeps it rather than guessing.
func TestDiffNeverClearsASecret(t *testing.T) {
c := diffSchema(t)
base, _ := c.Validate(diffDoc())
next := base.Clone()
next.Secrets = map[string]string{}
if p := c.Diff(base, next); len(p.Secrets) != 0 {
t.Fatalf("Diff decided to clear a secret on its own: %v", p.Secrets)
}
}