|
| 1 | +package patch_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/json" |
| 5 | + "reflect" |
| 6 | + "testing" |
| 7 | + "time" |
| 8 | + |
| 9 | + "github.com/ggicci/patch" |
| 10 | +) |
| 11 | + |
| 12 | +func shouldBeNil(t *testing.T, err error, failMessage string) { |
| 13 | + if err != nil { |
| 14 | + t.Logf("%s, got error: %v", failMessage, err) |
| 15 | + t.Fail() |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +func shouldResemble(t *testing.T, va, vb any, failMessage string) { |
| 20 | + if reflect.DeepEqual(va, vb) { |
| 21 | + return |
| 22 | + } |
| 23 | + t.Logf("%s, expected %#v, got %#v", failMessage, va, vb) |
| 24 | + t.Fail() |
| 25 | +} |
| 26 | + |
| 27 | +func fixedZone(offset int) *time.Location { |
| 28 | + if offset == 0 { |
| 29 | + return time.UTC |
| 30 | + } |
| 31 | + _, localOffset := time.Now().Local().Zone() |
| 32 | + if offset == localOffset { |
| 33 | + return time.Local |
| 34 | + } |
| 35 | + return time.FixedZone("", offset) |
| 36 | +} |
| 37 | + |
| 38 | +func testJSONMarshalling(t *testing.T, tc testcase) { |
| 39 | + bs, err := json.Marshal(tc.Expected) |
| 40 | + if err != nil { |
| 41 | + t.Logf("marshal failed, got error: %v", err) |
| 42 | + t.Fail() |
| 43 | + } |
| 44 | + if string(bs) != tc.Content { |
| 45 | + t.Logf("marshal failed, expected %q, got %q", tc.Content, string(bs)) |
| 46 | + t.Fail() |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +func testJSONUnmarshalling(t *testing.T, tc testcase) { |
| 51 | + rt := reflect.TypeOf(tc.Expected) // type: patch.Field |
| 52 | + rv := reflect.New(rt) // rv: *patch.Field |
| 53 | + |
| 54 | + shouldBeNil(t, json.Unmarshal([]byte(tc.Content), rv.Interface()), "unmarshal failed") |
| 55 | + shouldResemble(t, rv.Elem().Interface(), tc.Expected, "unmarshal failed") |
| 56 | +} |
| 57 | + |
| 58 | +type testcase struct { |
| 59 | + Content string |
| 60 | + Expected any |
| 61 | +} |
| 62 | + |
| 63 | +type GitHubProfile struct { |
| 64 | + Id int64 `json:"id"` |
| 65 | + Login string `json:"login"` |
| 66 | + AvatarUrl string `json:"avatar_url"` |
| 67 | +} |
| 68 | + |
| 69 | +type GenderType string |
| 70 | + |
| 71 | +type Account struct { |
| 72 | + Id int64 |
| 73 | + Email string |
| 74 | + Tags []string |
| 75 | + Gender GenderType |
| 76 | + GitHub *GitHubProfile |
| 77 | +} |
| 78 | + |
| 79 | +type AccountPatch struct { |
| 80 | + Email patch.Field[string] `json:"email"` |
| 81 | + Tags patch.Field[[]string] `json:"tags"` |
| 82 | + Gender patch.Field[GenderType] `json:"gender"` |
| 83 | + GitHub patch.Field[*GitHubProfile] `json:"github"` |
| 84 | +} |
| 85 | + |
| 86 | +func TestField(t *testing.T) { |
| 87 | + var cases = []testcase{ |
| 88 | + {"true", patch.Field[bool]{true, true}}, |
| 89 | + {"false", patch.Field[bool]{false, true}}, |
| 90 | + {"2045", patch.Field[int]{2045, true}}, |
| 91 | + {"127", patch.Field[int8]{127, true}}, |
| 92 | + {"32767", patch.Field[int16]{32767, true}}, |
| 93 | + {"2147483647", patch.Field[int32]{2147483647, true}}, |
| 94 | + {"9223372036854775807", patch.Field[int64]{9223372036854775807, true}}, |
| 95 | + {"2045", patch.Field[uint]{2045, true}}, |
| 96 | + {"255", patch.Field[uint8]{255, true}}, |
| 97 | + {"65535", patch.Field[uint16]{65535, true}}, |
| 98 | + {"4294967295", patch.Field[uint32]{4294967295, true}}, |
| 99 | + {"18446744073709551615", patch.Field[uint64]{18446744073709551615, true}}, |
| 100 | + {"3.14", patch.Field[float32]{3.14, true}}, |
| 101 | + {"3.14", patch.Field[float64]{3.14, true}}, |
| 102 | + {"\"hello\"", patch.Field[string]{"hello", true}}, |
| 103 | + |
| 104 | + // Array |
| 105 | + {`[true,false]`, patch.Field[[]bool]{[]bool{true, false}, true}}, |
| 106 | + {"[1,2,3]", patch.Field[[]int]{[]int{1, 2, 3}, true}}, |
| 107 | + {"[1,2,3]", patch.Field[[]int8]{[]int8{1, 2, 3}, true}}, |
| 108 | + {"[1,2,3]", patch.Field[[]int16]{[]int16{1, 2, 3}, true}}, |
| 109 | + {"[1,2,3]", patch.Field[[]int32]{[]int32{1, 2, 3}, true}}, |
| 110 | + {"[1,2,3]", patch.Field[[]int64]{[]int64{1, 2, 3}, true}}, |
| 111 | + {"[1,2,3]", patch.Field[[]uint]{[]uint{1, 2, 3}, true}}, |
| 112 | + // NOTE(ggicci): []uint8 is a special case, check TestFieldUint8Array |
| 113 | + {"[1,2,3]", patch.Field[[]uint16]{[]uint16{1, 2, 3}, true}}, |
| 114 | + {"[1,2,3]", patch.Field[[]uint32]{[]uint32{1, 2, 3}, true}}, |
| 115 | + {"[1,2,3]", patch.Field[[]uint64]{[]uint64{1, 2, 3}, true}}, |
| 116 | + {"[0.618,1,3.14]", patch.Field[[]float32]{[]float32{0.618, 1, 3.14}, true}}, |
| 117 | + {"[0.618,1,3.14]", patch.Field[[]float64]{[]float64{0.618, 1, 3.14}, true}}, |
| 118 | + {`["hello","world"]`, patch.Field[[]string]{[]string{"hello", "world"}, true}}, |
| 119 | + |
| 120 | + // time.Time |
| 121 | + { |
| 122 | + `"2019-08-25T07:19:34Z"`, |
| 123 | + patch.Field[time.Time]{ |
| 124 | + time.Date(2019, 8, 25, 7, 19, 34, 0, fixedZone(0)), |
| 125 | + true, |
| 126 | + }, |
| 127 | + }, |
| 128 | + { |
| 129 | + `"1991-11-10T08:00:00-07:00"`, |
| 130 | + patch.Field[time.Time]{ |
| 131 | + time.Date(1991, 11, 10, 8, 0, 0, 0, fixedZone(-7*3600)), |
| 132 | + true, |
| 133 | + }, |
| 134 | + }, |
| 135 | + { |
| 136 | + `"1991-11-10T08:00:00+08:00"`, |
| 137 | + patch.Field[time.Time]{ |
| 138 | + time.Date(1991, 11, 10, 8, 0, 0, 0, fixedZone(+8*3600)), |
| 139 | + true, |
| 140 | + }, |
| 141 | + }, |
| 142 | + |
| 143 | + // Custom structs |
| 144 | + { |
| 145 | + `{"Id":1000,"Email":"ggicci@example.com","Tags":["developer","修勾"],"Gender":"male","GitHub":{"id":3077555,"login":"ggicci","avatar_url":"https://avatars.githubusercontent.com/u/3077555?v=4"}}`, |
| 146 | + patch.Field[*Account]{ |
| 147 | + &Account{ |
| 148 | + Id: 1000, |
| 149 | + Email: "ggicci@example.com", |
| 150 | + Tags: []string{"developer", "修勾"}, |
| 151 | + Gender: "male", |
| 152 | + GitHub: &GitHubProfile{ |
| 153 | + Id: 3077555, |
| 154 | + Login: "ggicci", |
| 155 | + AvatarUrl: "https://avatars.githubusercontent.com/u/3077555?v=4", |
| 156 | + }, |
| 157 | + }, |
| 158 | + true, |
| 159 | + }, |
| 160 | + }, |
| 161 | + } |
| 162 | + |
| 163 | + for _, c := range cases { |
| 164 | + testJSONMarshalling(t, c) |
| 165 | + testJSONUnmarshalling(t, c) |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +// TestFieldUint8Array runs JSON marshalling & unmarshalling tests on type Field[[]uint8]. |
| 170 | +// Because in golang's encoding/json package, encoding uint8[] is special. |
| 171 | +// See: https://golang.org/pkg/encoding/json/#Marshal |
| 172 | +// |
| 173 | +// > Array and slice values encode as JSON arrays, except that []byte encodes |
| 174 | +// as a base64-encoded string, and a nil slice encodes as the null JSON |
| 175 | +// value. |
| 176 | +// |
| 177 | +// uint8 the set of all unsigned 8-bit integers (0 to 255) |
| 178 | +// byte alias for uint8 |
| 179 | +func TestFieldUint8Array(t *testing.T) { |
| 180 | + var a1 patch.Field[[]uint8] |
| 181 | + // unmarshal |
| 182 | + shouldBeNil(t, json.Unmarshal([]byte("[1,2,3]"), &a1), "unmarshal Field[[]uint8] failed") |
| 183 | + shouldResemble(t, patch.Field[[]uint8]{[]uint8{1, 2, 3}, true}, a1, "unmarshal Field[[]uint8] failed") |
| 184 | + |
| 185 | + // marshal |
| 186 | + var a2 = patch.Field[[]uint8]{[]uint8{1, 2, 3}, true} |
| 187 | + out, err := json.Marshal(a2) |
| 188 | + shouldBeNil(t, err, "marshal Field[[]uint8] failed") |
| 189 | + shouldResemble(t, `"AQID"`, string(out), "marshal Field[[]uint8] failed") |
| 190 | +} |
| 191 | + |
| 192 | +func TestPatchStructs(t *testing.T) { |
| 193 | + var testcases = []testcase{ |
| 194 | + { |
| 195 | + `{"email":"ggicci.2@example.com","tags":["artist","photographer"]}`, |
| 196 | + AccountPatch{ |
| 197 | + Email: patch.Field[string]{"ggicci.2@example.com", true}, |
| 198 | + Gender: patch.Field[GenderType]{"", false}, |
| 199 | + Tags: patch.Field[[]string]{[]string{"artist", "photographer"}, true}, |
| 200 | + GitHub: patch.Field[*GitHubProfile]{nil, false}, |
| 201 | + }, |
| 202 | + }, |
| 203 | + { |
| 204 | + `{"tags":null,"gender":"female","github":{"id":100,"login":"ggicci.2","avatar_url":null}}`, |
| 205 | + AccountPatch{ |
| 206 | + Email: patch.Field[string]{"", false}, |
| 207 | + Gender: patch.Field[GenderType]{"female", true}, |
| 208 | + Tags: patch.Field[[]string]{nil, true}, |
| 209 | + GitHub: patch.Field[*GitHubProfile]{&GitHubProfile{ |
| 210 | + Id: 100, |
| 211 | + Login: "ggicci.2", |
| 212 | + AvatarUrl: "", |
| 213 | + }, true}, |
| 214 | + }, |
| 215 | + }, |
| 216 | + } |
| 217 | + |
| 218 | + for _, c := range testcases { |
| 219 | + testJSONUnmarshalling(t, c) |
| 220 | + } |
| 221 | +} |
0 commit comments