1/*
2 * Copyright 2011-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <folly/json.h>
18#include <folly/json_patch.h>
19#include <folly/json_pointer.h>
20#include <folly/portability/GMock.h>
21#include <folly/portability/GTest.h>
22
23using folly::dynamic;
24using folly::json_patch;
25using folly::json_pointer;
26
27using err_code = folly::json_patch::parse_error_code;
28using op_code = folly::json_patch::patch_operation_code;
29
30class JsonPatchTest : public ::testing::Test {};
31
32TEST_F(JsonPatchTest, ValidPatch) {
33 // from RFC 6902
34 constexpr folly::StringPiece jsonPatchStr = R"(
35 [
36 { "op": "test", "path": "/a/b/c", "value": "foo" },
37 { "op": "remove", "path": "/a/b/c" },
38 { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
39 { "op": "replace", "path": "/a/b/c", "value": 42 },
40 { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
41 { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
42 ])";
43 auto const expected = std::vector<json_patch::patch_operation>{
44 {op_code::test,
45 json_pointer::parse("/a/b/c"),
46 folly::none,
47 dynamic("foo")},
48 {op_code::remove,
49 json_pointer::parse("/a/b/c"),
50 folly::none,
51 folly::none},
52 {op_code::add,
53 json_pointer::parse("/a/b/c"),
54 folly::none,
55 folly::parseJson(R"(["foo", "bar"])")},
56 {op_code::replace,
57 json_pointer::parse("/a/b/c"),
58 folly::none,
59 dynamic(42)},
60 {op_code::move,
61 json_pointer::parse("/a/b/d"),
62 json_pointer::parse("/a/b/c"),
63 folly::none},
64 {op_code::copy,
65 json_pointer::parse("/a/b/e"),
66 json_pointer::parse("/a/b/d"),
67 folly::none}};
68 auto const parsed =
69 json_patch::try_parse(folly::parseJson(jsonPatchStr)).value().ops();
70 EXPECT_EQ(expected, parsed);
71}
72
73TEST_F(JsonPatchTest, InvalidPatches) {
74 EXPECT_EQ(
75 err_code::invalid_shape,
76 json_patch::try_parse(dynamic::object()).error().error_code);
77
78 EXPECT_EQ(
79 err_code::invalid_shape,
80 json_patch::try_parse(dynamic::array(dynamic::array()))
81 .error()
82 .error_code);
83
84 EXPECT_EQ(
85 err_code::missing_op,
86 json_patch::try_parse(folly::parseJson(R"([{"path": "/a/b/c"}])"))
87 .error()
88 .error_code);
89
90 EXPECT_EQ(
91 err_code::unknown_op,
92 json_patch::try_parse(
93 folly::parseJson(R"([{"op": "blah", "path": "/a/b/c"}])"))
94 .error()
95 .error_code);
96
97 EXPECT_EQ(
98 err_code::malformed_op,
99 json_patch::try_parse(
100 folly::parseJson(R"([{"op": ["blah"], "path": "/a/b/c"}])"))
101 .error()
102 .error_code);
103
104 EXPECT_EQ(
105 err_code::missing_path_attr,
106 json_patch::try_parse(folly::parseJson(R"([{"op": "test"}])"))
107 .error()
108 .error_code);
109
110 EXPECT_EQ(
111 err_code::malformed_path_attr,
112 json_patch::try_parse(
113 folly::parseJson(R"([{"op": "test", "path" : "a/z/x"}])"))
114 .error()
115 .error_code);
116
117 EXPECT_EQ(
118 err_code::malformed_path_attr,
119 json_patch::try_parse(
120 folly::parseJson(R"([{"op": "test", "path" : ["a/z/x"]}])"))
121 .error()
122 .error_code);
123
124 EXPECT_EQ(
125 err_code::missing_from_attr,
126 json_patch::try_parse(
127 folly::parseJson(R"([{"op": "copy", "path" : "/a/b/c"}])"))
128 .error()
129 .error_code);
130
131 EXPECT_EQ(
132 err_code::malformed_from_attr,
133 json_patch::try_parse(
134 folly::parseJson(
135 R"([{"op": "copy", "from" : "c/d/e", "path" : "/a/b/c"}])"))
136 .error()
137 .error_code);
138
139 EXPECT_EQ(
140 err_code::overlapping_pointers,
141 json_patch::try_parse(
142 folly::parseJson(
143 R"([{"op": "move", "from" : "/a/b/c", "path" : "/a/b/c"}])"))
144 .error()
145 .error_code);
146
147 EXPECT_EQ(
148 err_code::overlapping_pointers,
149 json_patch::try_parse(
150 folly::parseJson(
151 R"([{"op": "move", "from" : "/a/b/c", "path" : "/a/b/c/d"}])"))
152 .error()
153 .error_code);
154
155 // validate presence of mandatory per-operation attributes
156
157 EXPECT_EQ(
158 err_code::missing_value_attr,
159 json_patch::try_parse(
160 folly::parseJson(R"([{"op": "test", "path" : "/a/b/c"}])"))
161 .error()
162 .error_code);
163
164 EXPECT_EQ(
165 err_code::missing_value_attr,
166 json_patch::try_parse(
167 folly::parseJson(R"([{"op": "replace", "path" : "/a/b/c"}])"))
168 .error()
169 .error_code);
170
171 EXPECT_EQ(
172 err_code::missing_from_attr,
173 json_patch::try_parse(
174 folly::parseJson(R"([{"op": "move", "path" : "/a/b/c"}])"))
175 .error()
176 .error_code);
177
178 EXPECT_EQ(
179 err_code::missing_from_attr,
180 json_patch::try_parse(
181 folly::parseJson(R"([{"op": "copy", "path" : "/a/b/c"}])"))
182 .error()
183 .error_code);
184
185 // test the object reference in error: in patch below, 3rd entry is incorrect
186
187 constexpr folly::StringPiece jsonPatchStr = R"(
188 [
189 { "op": "test", "path": "/a/b/c", "value": "foo" },
190 { "op": "remove", "path": "/a/b/c" },
191 { "op": "add", "path": "/a/b/c" }
192 ])";
193 auto jsonObj = folly::parseJson(jsonPatchStr);
194 auto err = json_patch::try_parse(jsonObj).error();
195 EXPECT_EQ(err_code::missing_value_attr, err.error_code);
196 // the invalid entry - check pointers and values they point at
197 EXPECT_EQ(&jsonObj[2], err.obj);
198 EXPECT_EQ(jsonObj[2], *err.obj);
199}
200
201TEST_F(JsonPatchTest, SuccessfulPatchApplication) {
202 constexpr folly::StringPiece jsonPatchStr = R"(
203 [
204 { "op": "test", "path": "/a/b/c", "value": "foo" },
205 { "op": "remove", "path": "/a/b/c" },
206 { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
207 { "op": "replace", "path": "/a/b/c", "value": 42 },
208 { "op": "test", "path": "/a/b/c", "value": 42 },
209 { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
210 { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" },
211 { "op": "add", "path": "/a/b/c", "value": [1, 2, 3] },
212 { "op": "add", "path": "/a/b/c/1", "value": 100 }
213 ])";
214 auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value();
215
216 dynamic objToMutate =
217 dynamic::object("a", dynamic::object("b", dynamic::object("c", "foo")));
218
219 auto res = patch.apply(objToMutate);
220
221 EXPECT_FALSE(res.hasError()) << (int)res.error().error_code;
222
223 EXPECT_EQ(42, objToMutate["a"]["b"]["d"].asInt());
224 EXPECT_EQ(42, objToMutate["a"]["b"]["e"].asInt());
225 EXPECT_EQ(100, objToMutate["a"]["b"]["c"][1].asInt());
226 EXPECT_EQ(1, objToMutate["a"]["b"]["c"][0].asInt());
227}
228
229TEST_F(JsonPatchTest, TestOpFailure) {
230 constexpr folly::StringPiece jsonPatchStr = R"(
231 [
232 { "op": "test", "path": "/a/b/c", "value": "foo" },
233 { "op": "remove", "path": "/a/b/c" }
234 ])";
235 auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value();
236
237 dynamic objToMutate =
238 dynamic::object("a", dynamic::object("b", dynamic::object("c", "bar")));
239
240 auto res = patch.apply(objToMutate);
241
242 EXPECT_TRUE(res.hasError());
243 EXPECT_EQ(
244 json_patch::patch_application_error_code::test_failed,
245 res.error().error_code);
246
247 EXPECT_EQ("bar", objToMutate["a"]["b"]["c"].asString());
248}
249
250TEST_F(JsonPatchTest, PathNotFound) {
251 constexpr folly::StringPiece jsonPatchStr = R"(
252 [
253 { "op": "remove", "path": "/a/b/d" }
254 ])";
255 auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value();
256
257 dynamic objToMutate =
258 dynamic::object("a", dynamic::object("b", dynamic::object("c", "foo")));
259
260 auto res = patch.apply(objToMutate);
261
262 EXPECT_TRUE(res.hasError());
263 EXPECT_EQ(
264 json_patch::patch_application_error_code::path_not_found,
265 res.error().error_code);
266
267 EXPECT_EQ("foo", objToMutate["a"]["b"]["c"].asString());
268}
269
270TEST_F(JsonPatchTest, FromNotFound) {
271 constexpr folly::StringPiece jsonPatchStr = R"(
272 [
273 { "op": "copy", "from": "/a/c/b", "path": "/a/b/d" }
274 ])";
275 auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value();
276
277 dynamic objToMutate =
278 dynamic::object("a", dynamic::object("b", dynamic::object("c", "foo")));
279
280 auto res = patch.apply(objToMutate);
281
282 EXPECT_TRUE(res.hasError());
283 EXPECT_EQ(
284 json_patch::patch_application_error_code::from_not_found,
285 res.error().error_code);
286
287 EXPECT_THROW(objToMutate["a"]["b"].at("d"), std::out_of_range);
288}
289
290TEST_F(JsonPatchTest, RemoveRootObject) {
291 constexpr folly::StringPiece jsonPatchStr = R"(
292 [
293 { "op": "remove", "path": "" }
294 ])";
295 auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value();
296
297 dynamic objToMutate =
298 dynamic::object("a", dynamic::object("b", dynamic::object("c", "foo")));
299
300 auto res = patch.apply(objToMutate);
301
302 EXPECT_TRUE(res.hasError());
303 EXPECT_EQ(
304 json_patch::patch_application_error_code::other, res.error().error_code);
305
306 EXPECT_EQ("foo", objToMutate["a"]["b"]["c"].asString());
307}
308
309TEST_F(JsonPatchTest, AddWithAppend) {
310 constexpr folly::StringPiece jsonPatchStr = R"(
311 [
312 { "op": "add", "path": "/a/b/-", "value": 100 }
313 ])";
314 auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value();
315
316 dynamic objToMutate =
317 dynamic::object("a", dynamic::object("b", dynamic::array(1, 2, 3, 4)));
318
319 auto res = patch.apply(objToMutate);
320
321 EXPECT_TRUE(res.hasValue());
322 EXPECT_EQ(100, objToMutate["a"]["b"][4].asInt());
323}
324
325TEST_F(JsonPatchTest, RemoveWithMinus) {
326 constexpr folly::StringPiece jsonPatchStr = R"(
327 [
328 { "op": "remove", "path": "/a/b/-" }
329 ])";
330 auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value();
331
332 dynamic objToMutate =
333 dynamic::object("a", dynamic::object("b", dynamic::array(1, 2, 3, 4)));
334
335 auto res = patch.apply(objToMutate);
336
337 EXPECT_TRUE(res.hasError());
338 EXPECT_EQ(
339 json_patch::patch_application_error_code::path_not_found,
340 res.error().error_code);
341}
342
343TEST_F(JsonPatchTest, FailedOpIndex) {
344 constexpr folly::StringPiece jsonPatchStr = R"(
345 [
346 { "op": "test", "path": "/a/b/0", "value": 1 },
347 { "op": "test", "path": "/a/b/0", "value": 2 }
348 ])";
349 auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value();
350
351 dynamic objToMutate =
352 dynamic::object("a", dynamic::object("b", dynamic::array(1, 2, 3, 4)));
353
354 auto res = patch.apply(objToMutate);
355
356 EXPECT_TRUE(res.hasError());
357 EXPECT_EQ(
358 json_patch::patch_application_error_code::test_failed,
359 res.error().error_code);
360 EXPECT_EQ(1, res.error().index);
361}
362