2929use Throwable ;
3030
3131#[CoversClass(PatchOperationList::class)]
32- #[UsesClass (Add::class)]
33- #[UsesClass (Copy::class)]
34- #[UsesClass (Move::class)]
35- #[UsesClass (Remove::class)]
36- #[UsesClass (Replace::class)]
37- #[UsesClass (Test::class)]
38- #[UsesClass (PatchOperation::class)]
32+ #[CoversClass (Add::class)]
33+ #[CoversClass (Copy::class)]
34+ #[CoversClass (Move::class)]
35+ #[CoversClass (Remove::class)]
36+ #[CoversClass (Replace::class)]
37+ #[CoversClass (Test::class)]
38+ #[CoversClass (PatchOperation::class)]
3939#[UsesClass(FastJsonPatchExceptionTrait::class)]
4040#[UsesClass(InvalidPatchException::class)]
4141#[UsesClass(InvalidPatchOperationException::class)]
@@ -192,7 +192,7 @@ public function testItCanDecodeWithCustomOperationClasses(): void
192192 $ result = PatchOperationList::fromJson (
193193 <<<'JSON'
194194 [
195- {"op": "add", "path": "/greeting", "value": "Hello" },
195+ {"op": "add", "path": "/greeting", "value": "Hello", "custom": "my own var" },
196196 {"op": "append", "path": "/greeting", "suffix": " World" },
197197 {"op": "copy", "path": "/whatever", "from": "/greeting"}
198198 ]
@@ -205,7 +205,7 @@ public function testItCanDecodeWithCustomOperationClasses(): void
205205
206206 $ this ->assertEquals (
207207 new PatchOperationList (
208- new CustomAdd ('/greeting ' , 'Hello ' ),
208+ new CustomAdd ('/greeting ' , 'Hello ' , ' my own var ' ),
209209 new Append ('/greeting ' , ' World ' ),
210210 new Copy (path: '/whatever ' , from: '/greeting ' ),
211211 ),
@@ -234,11 +234,75 @@ public static function invalidJsonProvider(): array
234234 InvalidPatchException::class,
235235 'Invalid patch structure (expected list, got stdClass) ' ,
236236 ],
237+ 'patch item is not an object (example 1) ' => [
238+ '[["foo"]] ' ,
239+ InvalidPatchOperationException::class,
240+ 'Each patch item must be an object, got array ' ,
241+ ],
242+ 'patch item is not an object (example 2) ' => [
243+ '[{"op": "remove", "path": "/some/path"}, true] ' ,
244+ InvalidPatchOperationException::class,
245+ 'Each patch item must be an object, got bool ' ,
246+ ],
247+ 'patch without op property ' => [
248+ '[{"path": "/some/path", "value": "anything"}] ' ,
249+ InvalidPatchOperationException::class,
250+ 'Each patch item must specify "op" ' ,
251+ ],
252+ 'op is invalid type ' => [
253+ '[{"op": true}] ' ,
254+ InvalidPatchOperationException::class,
255+ 'Patch "op" must be a string, got bool ' ,
256+ ],
237257 'unknown operation ' => [
238258 '[{"op": "scramble", "path": "/anywhere"}] ' ,
239259 InvalidPatchOperationException::class,
240260 'Unknown operation "scramble" ' ,
241261 ],
262+ 'unexpected extra operation property ' => [
263+ '[{"op":"add", "path": "/foo", "value": "bar", "custom": "things"}] ' ,
264+ InvalidPatchOperationException::class,
265+ // The message also contains the original PHP message but this varies between versions so we do not
266+ // include it in the assertion.
267+ sprintf (
268+ 'Unexpected param(s) for add operation as %s ' ,
269+ Add::class,
270+ ),
271+ ],
272+ 'missing operation property ' => [
273+ '[{"op":"add", "path": "/foo"}] ' ,
274+ InvalidPatchOperationException::class,
275+ // The message also contains the original PHP message but this varies between versions so we do not
276+ // include it in the assertion.
277+ sprintf (
278+ 'Missing required param(s) for add operation as %s ' ,
279+ Add::class,
280+ ),
281+ ],
282+ 'operation property has incorrect type ' => [
283+ '[{"op":"add", "path": true, "value": "bar"}] ' ,
284+ InvalidPatchOperationException::class,
285+ // The message also contains the original PHP message but this varies between versions so we do not
286+ // include it in the assertion.
287+ sprintf (
288+ 'Invalid param(s) for add operation as %s ' ,
289+ Add::class,
290+ ),
291+ ],
292+ 'operation with mixed string and int keys (example 1) ' => [
293+ // PHP would internally convert this into positional args and accept it even though there's no guarantee
294+ // we have the right keys in the right sequence / have no extra properties.
295+ '[{"op":"add", "0": "bar", "1": "/from"}] ' ,
296+ InvalidPatchOperationException::class,
297+ 'All patch operation properties must have string names '
298+ ],
299+ 'operation with mixed string and int keys (example 2) ' => [
300+ // PHP would internally convert this into positional args and accept it even though there's no guarantee
301+ // we have the right keys in the right sequence / have no extra properties.
302+ '[{"op":"add", "1": "bar", "2": "/from"}] ' ,
303+ InvalidPatchOperationException::class,
304+ 'All patch operation properties must have string names '
305+ ]
242306 ];
243307 }
244308
@@ -272,6 +336,7 @@ public function __construct(
272336 public function __construct (
273337 public string $ path ,
274338 public mixed $ value ,
339+ public mixed $ custom ,
275340 ) {
276341 parent ::__construct ('add ' );
277342 }
0 commit comments