Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2058926

Browse files
authoredJan 29, 2018
Merge pull request #4 from swaggest/v2-patch
passing additional spec tests
2 parents 16f0445 + cf093c8 commit 2058926

15 files changed

+2170
-45
lines changed
 

‎.travis.yml

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ cache:
1919

2020
# execute any number of scripts before the test run, custom env's are available as variables
2121
before_script:
22+
- ls -la $HOME/.composer/cache
23+
- test -f $HOME/.composer/cache/composer.lock.$(phpenv version-name) && cp $HOME/.composer/cache/composer.lock.$(phpenv version-name) ./composer.lock || echo "No composer.lock cached"
2224
- composer install --dev --no-interaction --prefer-dist
23-
# - cat composer.lock
25+
- test -f $HOME/.composer/cache/composer.lock.$(phpenv version-name) || cp ./composer.lock $HOME/.composer/cache/composer.lock.$(phpenv version-name)
26+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/phpstan.phar || wget https://github.com/phpstan/phpstan/releases/download/0.9.1/phpstan.phar -O $HOME/.composer/cache/phpstan.phar; fi
27+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/ocular.phar || wget https://scrutinizer-ci.com/ocular.phar -O $HOME/.composer/cache/ocular.phar; fi
28+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/cctr || wget https://codeclimate.com/downloads/test-reporter/test-reporter-0.1.4-linux-amd64 -O $HOME/.composer/cache/cctr && chmod +x $HOME/.composer/cache/cctr; fi
29+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then $HOME/.composer/cache/cctr before-build; fi
2430

2531
matrix:
2632
allow_failures:
@@ -29,11 +35,9 @@ matrix:
2935
fast_finish: true
3036

3137
script:
32-
- mkdir -p build/logs
33-
- ./vendor/bin/phpunit -v --configuration phpunit.xml --coverage-clover build/logs/clover.xml
38+
- ./vendor/bin/phpunit -v --configuration phpunit.xml --coverage-text --coverage-clover clover.xml
39+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then php $HOME/.composer/cache/phpstan.phar analyze -l 7 ./src; fi
3440

3541
after_script:
36-
- wget https://scrutinizer-ci.com/ocular.phar
37-
- php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.clover
38-
- if [[ $(phpenv version-name) =~ 7.1 ]] ; then php vendor/bin/coveralls -v; fi
39-
- if [[ $(phpenv version-name) =~ 7.1 ]] ; then php vendor/bin/test-reporter; fi
42+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then php $HOME/.composer/cache/ocular.phar code-coverage:upload --format=php-clover clover.xml; fi
43+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then $HOME/.composer/cache/cctr after-build --exit-code $TRAVIS_TEST_RESULT; fi

‎src/Cli/Apply.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ public function performAction()
4949
$patch = JsonPatch::import(json_decode($patchJson));
5050
$base = json_decode($baseJson);
5151
$patch->apply($base);
52+
$this->out = $base;
5253
} catch (Exception $e) {
5354
$this->response->error($e->getMessage());
5455
}
55-
$this->out = $base;
5656

5757
$this->postPerform();
5858
}

‎src/Cli/Base.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Swaggest\JsonDiff\Cli;
44

55

6+
use Swaggest\JsonDiff\Exception;
67
use Swaggest\JsonDiff\JsonDiff;
78
use Yaoi\Command;
89

@@ -48,7 +49,12 @@ protected function prePerform()
4849
if ($this->rearrangeArrays) {
4950
$options += JsonDiff::REARRANGE_ARRAYS;
5051
}
51-
$this->diff = new JsonDiff(json_decode($originalJson), json_decode($newJson), $options);
52+
try {
53+
$this->diff = new JsonDiff(json_decode($originalJson), json_decode($newJson), $options);
54+
} catch (Exception $e) {
55+
$this->response->error($e->getMessage());
56+
return;
57+
}
5258

5359
$this->out = '';
5460
}

‎src/Exception.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55

66
class Exception extends \Exception
77
{
8-
8+
const EMPTY_PROPERTY_NAME_UNSUPPORTED = 1;
99
}

‎src/JsonDiff.php

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ class JsonDiff
4545

4646
/**
4747
* Processor constructor.
48-
* @param $original
49-
* @param $new
48+
* @param mixed $original
49+
* @param mixed $new
5050
* @param int $options
51+
* @throws Exception
5152
*/
5253
public function __construct($original, $new, $options = 0)
5354
{
@@ -181,11 +182,21 @@ public function getPatch()
181182
return $this->jsonPatch;
182183
}
183184

185+
/**
186+
* @return array|null|object|\stdClass
187+
* @throws Exception
188+
*/
184189
private function rearrange()
185190
{
186191
return $this->process($this->original, $this->new);
187192
}
188193

194+
/**
195+
* @param mixed $original
196+
* @param mixed $new
197+
* @return array|null|object|\stdClass
198+
* @throws Exception
199+
*/
189200
private function process($original, $new)
190201
{
191202
if (
@@ -194,6 +205,9 @@ private function process($original, $new)
194205
) {
195206
if ($original !== $new) {
196207
$this->modifiedCnt++;
208+
if ($this->options & self::STOP_ON_DIFF) {
209+
return null;
210+
}
197211
$this->modifiedPaths [] = $this->path;
198212

199213
$this->jsonPatch->op(new Test($this->path, $original));
@@ -202,9 +216,6 @@ private function process($original, $new)
202216
JsonPointer::add($this->modifiedOriginal, $this->pathItems, $original);
203217
JsonPointer::add($this->modifiedNew, $this->pathItems, $new);
204218

205-
if ($this->options & self::STOP_ON_DIFF) {
206-
return null;
207-
}
208219
}
209220
return $new;
210221
}
@@ -222,6 +233,12 @@ private function process($original, $new)
222233
$originalKeys = $original instanceof \stdClass ? get_object_vars($original) : $original;
223234

224235
foreach ($originalKeys as $key => $originalValue) {
236+
if ($this->options & self::STOP_ON_DIFF) {
237+
if ($this->modifiedCnt || $this->addedCnt || $this->removedCnt) {
238+
return null;
239+
}
240+
}
241+
225242
$path = $this->path;
226243
$pathItems = $this->pathItems;
227244
$this->path .= '/' . JsonPointer::escapeSegment($key, $this->options & self::JSON_URI_FRAGMENT_ID);
@@ -232,34 +249,34 @@ private function process($original, $new)
232249
unset($newArray[$key]);
233250
} else {
234251
$this->removedCnt++;
252+
if ($this->options & self::STOP_ON_DIFF) {
253+
return null;
254+
}
235255
$this->removedPaths [] = $this->path;
236256

237257
$this->jsonPatch->op(new Remove($this->path));
238258

239259
JsonPointer::add($this->removed, $this->pathItems, $originalValue);
240-
if ($this->options & self::STOP_ON_DIFF) {
241-
return null;
242-
}
243260
}
244261
$this->path = $path;
245262
$this->pathItems = $pathItems;
246263
}
247264

248265
// additions
249266
foreach ($newArray as $key => $value) {
267+
$this->addedCnt++;
268+
if ($this->options & self::STOP_ON_DIFF) {
269+
return null;
270+
}
250271
$newOrdered[$key] = $value;
251272
$path = $this->path . '/' . JsonPointer::escapeSegment($key, $this->options & self::JSON_URI_FRAGMENT_ID);
252273
$pathItems = $this->pathItems;
253274
$pathItems[] = $key;
254275
JsonPointer::add($this->added, $pathItems, $value);
255-
$this->addedCnt++;
256276
$this->addedPaths [] = $path;
257277

258278
$this->jsonPatch->op(new Add($path, $value));
259279

260-
if ($this->options & self::STOP_ON_DIFF) {
261-
return null;
262-
}
263280
}
264281

265282
return is_array($new) ? $newOrdered : (object)$newOrdered;

‎src/JsonPatch.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public function jsonSerialize()
108108
}
109109

110110
/**
111-
* @param $original
111+
* @param mixed $original
112112
* @throws Exception
113113
*/
114114
public function apply(&$original)
@@ -117,32 +117,34 @@ public function apply(&$original)
117117
$pathItems = JsonPointer::splitPath($operation->path);
118118
switch (true) {
119119
case $operation instanceof Add:
120-
JsonPointer::add($original, $pathItems, $operation->value);
120+
JsonPointer::add($original, $pathItems, $operation->value, false);
121121
break;
122122
case $operation instanceof Copy:
123123
$fromItems = JsonPointer::splitPath($operation->from);
124124
$value = JsonPointer::get($original, $fromItems);
125-
JsonPointer::add($original, $pathItems, $value);
125+
JsonPointer::add($original, $pathItems, $value, false);
126126
break;
127127
case $operation instanceof Move:
128128
$fromItems = JsonPointer::splitPath($operation->from);
129129
$value = JsonPointer::get($original, $fromItems);
130-
JsonPointer::add($original, $pathItems, $value);
131130
JsonPointer::remove($original, $fromItems);
131+
JsonPointer::add($original, $pathItems, $value, false);
132132
break;
133133
case $operation instanceof Remove:
134134
JsonPointer::remove($original, $pathItems);
135135
break;
136136
case $operation instanceof Replace:
137137
JsonPointer::get($original, $pathItems);
138-
JsonPointer::add($original, $pathItems, $operation->value);
138+
JsonPointer::remove($original, $pathItems);
139+
JsonPointer::add($original, $pathItems, $operation->value, false);
139140
break;
140141
case $operation instanceof Test:
141142
$value = JsonPointer::get($original, $pathItems);
142143
$diff = new JsonDiff($operation->value, $value,
143144
JsonDiff::STOP_ON_DIFF);
144145
if ($diff->getDiffCnt() !== 0) {
145-
throw new Exception('Test operation ' . json_encode($operation) . ' failed: ' . json_encode($value));
146+
throw new Exception('Test operation ' . json_encode($operation, JSON_UNESCAPED_SLASHES)
147+
. ' failed: ' . json_encode($value));
146148
}
147149
break;
148150
}

‎src/JsonPointer.php

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ public static function splitPath($path)
3131
$result = array();
3232
if ($first === '#') {
3333
foreach ($pathItems as $key) {
34-
$key = str_replace(array('~0', '~1'), array('~', '/'), urldecode($key));
34+
$key = str_replace(array('~1', '~0'), array('/', '~'), urldecode($key));
3535
$result[] = $key;
3636
}
3737
} else {
3838
if ($first !== '') {
3939
throw new Exception('Path must start with "/": ' . $path);
4040
}
4141
foreach ($pathItems as $key) {
42-
$key = str_replace(array('~0', '~1'), array('~', '/'), $key);
42+
$key = str_replace(array('~1', '~0'), array('/', '~'), $key);
4343
$result[] = $key;
4444
}
4545
}
@@ -50,22 +50,50 @@ public static function splitPath($path)
5050
* @param mixed $holder
5151
* @param string[] $pathItems
5252
* @param mixed $value
53+
* @param bool $recursively
54+
* @throws Exception
5355
*/
54-
public static function add(&$holder, $pathItems, $value)
56+
public static function add(&$holder, $pathItems, $value, $recursively = true)
5557
{
5658
$ref = &$holder;
5759
while (null !== $key = array_shift($pathItems)) {
5860
if ($ref instanceof \stdClass) {
61+
if (PHP_VERSION_ID < 71000 && '' === $key) {
62+
throw new Exception('Empty property name is not supported by PHP <7.1',
63+
Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
64+
}
65+
5966
$ref = &$ref->$key;
60-
} elseif ($ref === null
61-
&& !is_int($key)
62-
&& false === filter_var($key, FILTER_VALIDATE_INT)
63-
) {
64-
$key = (string)$key;
65-
$ref = new \stdClass();
66-
$ref = &$ref->{$key};
67-
} else {
68-
$ref = &$ref[$key];
67+
} else { // null or array
68+
$intKey = filter_var($key, FILTER_VALIDATE_INT);
69+
if ($ref === null && (false === $intKey || $intKey !== 0)) {
70+
$key = (string)$key;
71+
if ($recursively) {
72+
$ref = new \stdClass();
73+
$ref = &$ref->{$key};
74+
} else {
75+
throw new Exception('Non-existent path');
76+
}
77+
} else {
78+
if ($recursively && $ref === null) $ref = array();
79+
if ('-' === $key) {
80+
$ref = &$ref[];
81+
} else {
82+
if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
83+
array_splice($ref, $key, 0, array($value));
84+
}
85+
if (false === $intKey) {
86+
throw new Exception('Invalid key for array operation');
87+
}
88+
if ($intKey > count($ref) && !$recursively) {
89+
throw new Exception('Index is greater than number of items in array');
90+
} elseif ($intKey < 0) {
91+
throw new Exception('Negative index');
92+
}
93+
94+
$ref = &$ref[$intKey];
95+
}
96+
}
6997
}
7098
}
7199
$ref = $value;
@@ -108,6 +136,11 @@ public static function get($holder, $pathItems)
108136
$ref = $holder;
109137
while (null !== $key = array_shift($pathItems)) {
110138
if ($ref instanceof \stdClass) {
139+
if (PHP_VERSION_ID < 71000 && '' === $key) {
140+
throw new Exception('Empty property name is not supported by PHP <7.1',
141+
Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
142+
}
143+
111144
$vars = (array)$ref;
112145
if (self::arrayKeyExists($key, $vars)) {
113146
$ref = self::arrayGet($key, $vars);
@@ -159,6 +192,7 @@ public static function remove(&$holder, $pathItems)
159192
unset($parent->$refKey);
160193
} else {
161194
unset($parent[$refKey]);
195+
$parent = array_values($parent);
162196
}
163197
}
164198
return $ref;

‎tests/assets/spec-tests.json

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
[
2+
{
3+
"comment": "4.1. add with missing object",
4+
"doc": { "q": { "bar": 2 } },
5+
"patch": [ {"op": "add", "path": "/a/b", "value": 1} ],
6+
"error":
7+
"path /a does not exist -- missing objects are not created recursively"
8+
},
9+
10+
{
11+
"comment": "A.1. Adding an Object Member",
12+
"doc": {
13+
"foo": "bar"
14+
},
15+
"patch": [
16+
{ "op": "add", "path": "/baz", "value": "qux" }
17+
],
18+
"expected": {
19+
"baz": "qux",
20+
"foo": "bar"
21+
}
22+
},
23+
24+
{
25+
"comment": "A.2. Adding an Array Element",
26+
"doc": {
27+
"foo": [ "bar", "baz" ]
28+
},
29+
"patch": [
30+
{ "op": "add", "path": "/foo/1", "value": "qux" }
31+
],
32+
"expected": {
33+
"foo": [ "bar", "qux", "baz" ]
34+
}
35+
},
36+
37+
{
38+
"comment": "A.3. Removing an Object Member",
39+
"doc": {
40+
"baz": "qux",
41+
"foo": "bar"
42+
},
43+
"patch": [
44+
{ "op": "remove", "path": "/baz" }
45+
],
46+
"expected": {
47+
"foo": "bar"
48+
}
49+
},
50+
51+
{
52+
"comment": "A.4. Removing an Array Element",
53+
"doc": {
54+
"foo": [ "bar", "qux", "baz" ]
55+
},
56+
"patch": [
57+
{ "op": "remove", "path": "/foo/1" }
58+
],
59+
"expected": {
60+
"foo": [ "bar", "baz" ]
61+
}
62+
},
63+
64+
{
65+
"comment": "A.5. Replacing a Value",
66+
"doc": {
67+
"baz": "qux",
68+
"foo": "bar"
69+
},
70+
"patch": [
71+
{ "op": "replace", "path": "/baz", "value": "boo" }
72+
],
73+
"expected": {
74+
"baz": "boo",
75+
"foo": "bar"
76+
}
77+
},
78+
79+
{
80+
"comment": "A.6. Moving a Value",
81+
"doc": {
82+
"foo": {
83+
"bar": "baz",
84+
"waldo": "fred"
85+
},
86+
"qux": {
87+
"corge": "grault"
88+
}
89+
},
90+
"patch": [
91+
{ "op": "move", "from": "/foo/waldo", "path": "/qux/thud" }
92+
],
93+
"expected": {
94+
"foo": {
95+
"bar": "baz"
96+
},
97+
"qux": {
98+
"corge": "grault",
99+
"thud": "fred"
100+
}
101+
}
102+
},
103+
104+
{
105+
"comment": "A.7. Moving an Array Element",
106+
"doc": {
107+
"foo": [ "all", "grass", "cows", "eat" ]
108+
},
109+
"patch": [
110+
{ "op": "move", "from": "/foo/1", "path": "/foo/3" }
111+
],
112+
"expected": {
113+
"foo": [ "all", "cows", "eat", "grass" ]
114+
}
115+
116+
},
117+
118+
{
119+
"comment": "A.8. Testing a Value: Success",
120+
"doc": {
121+
"baz": "qux",
122+
"foo": [ "a", 2, "c" ]
123+
},
124+
"patch": [
125+
{ "op": "test", "path": "/baz", "value": "qux" },
126+
{ "op": "test", "path": "/foo/1", "value": 2 }
127+
],
128+
"expected": {
129+
"baz": "qux",
130+
"foo": [ "a", 2, "c" ]
131+
}
132+
},
133+
134+
{
135+
"comment": "A.9. Testing a Value: Error",
136+
"doc": {
137+
"baz": "qux"
138+
},
139+
"patch": [
140+
{ "op": "test", "path": "/baz", "value": "bar" }
141+
],
142+
"error": "string not equivalent"
143+
},
144+
145+
{
146+
"comment": "A.10. Adding a nested Member Object",
147+
"doc": {
148+
"foo": "bar"
149+
},
150+
"patch": [
151+
{ "op": "add", "path": "/child", "value": { "grandchild": { } } }
152+
],
153+
"expected": {
154+
"foo": "bar",
155+
"child": {
156+
"grandchild": {
157+
}
158+
}
159+
}
160+
},
161+
162+
{
163+
"comment": "A.11. Ignoring Unrecognized Elements",
164+
"doc": {
165+
"foo":"bar"
166+
},
167+
"patch": [
168+
{ "op": "add", "path": "/baz", "value": "qux", "xyz": 123 }
169+
],
170+
"expected": {
171+
"foo":"bar",
172+
"baz":"qux"
173+
}
174+
},
175+
176+
{
177+
"comment": "A.12. Adding to a Non-existent Target",
178+
"doc": {
179+
"foo": "bar"
180+
},
181+
"patch": [
182+
{ "op": "add", "path": "/baz/bat", "value": "qux" }
183+
],
184+
"error": "add to a non-existent target"
185+
},
186+
187+
{
188+
"comment": "A.13 Invalid JSON Patch Document",
189+
"doc": {
190+
"foo": "bar"
191+
},
192+
"patch": [
193+
{ "op": "add", "path": "/baz", "value": "qux", "op": "remove" }
194+
],
195+
"error": "operation has two 'op' members",
196+
"disabled": true
197+
},
198+
199+
{
200+
"comment": "A.14. ~ Escape Ordering",
201+
"doc": {
202+
"/": 9,
203+
"~1": 10
204+
},
205+
"patch": [{"op": "test", "path": "/~01", "value": 10}],
206+
"expected": {
207+
"/": 9,
208+
"~1": 10
209+
}
210+
},
211+
212+
{
213+
"comment": "A.15. Comparing Strings and Numbers",
214+
"doc": {
215+
"/": 9,
216+
"~1": 10
217+
},
218+
"patch": [{"op": "test", "path": "/~01", "value": "10"}],
219+
"error": "number is not equal to string"
220+
},
221+
222+
{
223+
"comment": "A.16. Adding an Array Value",
224+
"doc": {
225+
"foo": ["bar"]
226+
},
227+
"patch": [{ "op": "add", "path": "/foo/-", "value": ["abc", "def"] }],
228+
"expected": {
229+
"foo": ["bar", ["abc", "def"]]
230+
}
231+
}
232+
233+
]

‎tests/assets/tests.json

Lines changed: 1576 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/src/CliTest.php

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
namespace Swaggest\JsonDiff\Tests;
44

5+
use Swaggest\JsonDiff\Cli\App;
56
use Swaggest\JsonDiff\Cli\Apply;
67
use Swaggest\JsonDiff\Cli\Diff;
78
use Swaggest\JsonDiff\Cli\Info;
89
use Swaggest\JsonDiff\Cli\Rearrange;
10+
use Yaoi\Cli\Command\Application\Runner;
911
use Yaoi\Cli\Response;
1012

1113
class CliTest extends \PHPUnit_Framework_TestCase
@@ -21,7 +23,10 @@ public function testApply()
2123
ob_start();
2224
$d->performAction();
2325
$res = ob_get_clean();
24-
$this->assertSame(file_get_contents(__DIR__ . '/../../tests/assets/rearranged.json'), $res);
26+
$this->assertSame(
27+
file_get_contents(__DIR__ . '/../../tests/assets/rearranged.json'),
28+
str_replace("\r", '', $res)
29+
);
2530

2631
}
2732

@@ -36,7 +41,10 @@ public function testDiff()
3641
ob_start();
3742
$d->performAction();
3843
$res = ob_get_clean();
39-
$this->assertSame(file_get_contents(__DIR__ . '/../../tests/assets/patch.json'), $res);
44+
$this->assertSame(
45+
file_get_contents(__DIR__ . '/../../tests/assets/patch.json'),
46+
str_replace("\r", '', $res)
47+
);
4048
}
4149

4250
public function testRearrange()
@@ -50,14 +58,19 @@ public function testRearrange()
5058
ob_start();
5159
$d->performAction();
5260
$res = ob_get_clean();
53-
$this->assertSame(file_get_contents(__DIR__ . '/../../tests/assets/rearranged.json'), $res);
61+
$this->assertSame(
62+
file_get_contents(__DIR__ . '/../../tests/assets/rearranged.json'),
63+
str_replace("\r", '', $res)
64+
);
5465
}
5566

5667
public function testInfo()
5768
{
5869
$d = new Info();
5970
$d->pretty = true;
6071
$d->rearrangeArrays = true;
72+
$d->withContents = true;
73+
$d->withPaths = true;
6174
$d->originalPath = __DIR__ . '/../../tests/assets/original.json';
6275
$d->newPath = __DIR__ . '/../../tests/assets/new.json';
6376
$d->setResponse(new Response());
@@ -68,12 +81,78 @@ public function testInfo()
6881
{
6982
"addedCnt": 4,
7083
"modifiedCnt": 4,
71-
"removedCnt": 3
84+
"removedCnt": 3,
85+
"addedPaths": [
86+
"/key3/sub3",
87+
"/key4/1/c",
88+
"/key4/2/c",
89+
"/key5"
90+
],
91+
"modifiedPaths": [
92+
"/key1/0",
93+
"/key3/sub1",
94+
"/key3/sub2"
95+
],
96+
"removedPaths": [
97+
"/key2",
98+
"/key3/sub0",
99+
"/key4/1/b"
100+
],
101+
"added": {
102+
"key3": {
103+
"sub3": 0
104+
},
105+
"key4": {
106+
"1": {
107+
"c": false
108+
},
109+
"2": {
110+
"c": 1
111+
}
112+
},
113+
"key5": "wat"
114+
},
115+
"modifiedNew": {
116+
"key1": [
117+
5
118+
],
119+
"key3": {
120+
"sub1": "c",
121+
"sub2": false
122+
}
123+
},
124+
"modifiedOriginal": {
125+
"key1": [
126+
4
127+
],
128+
"key3": {
129+
"sub1": "a",
130+
"sub2": "b"
131+
}
132+
},
133+
"removed": {
134+
"key2": 2,
135+
"key3": {
136+
"sub0": 0
137+
},
138+
"key4": {
139+
"1": {
140+
"b": false
141+
}
142+
}
143+
}
72144
}
73145

74146
JSON
75-
, $res);
147+
, str_replace("\r", '', $res));
76148
}
77149

78150

151+
public function testApp()
152+
{
153+
ob_start();
154+
Runner::create(new App())->run();
155+
ob_end_clean();
156+
}
157+
79158
}

‎tests/src/DiffTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Swaggest\JsonDiff\Tests;
4+
5+
6+
use Swaggest\JsonDiff\JsonDiff;
7+
8+
class DiffTest extends \PHPUnit_Framework_TestCase
9+
{
10+
/**
11+
* @throws \Swaggest\JsonDiff\Exception
12+
*/
13+
public function testStopOnDiff()
14+
{
15+
$original = array(1, 2, 3, 4);
16+
$new = array(2, 4);
17+
$diff = new JsonDiff($original, $new, JsonDiff::STOP_ON_DIFF);
18+
$this->assertSame(1, $diff->getDiffCnt());
19+
}
20+
21+
}

‎tests/src/ExampleTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public function testDiff()
7575

7676
$diff = new JsonDiff(json_decode($originalJson), json_decode($newJson), JsonDiff::REARRANGE_ARRAYS);
7777
$this->assertEquals(json_decode($patchJson), $diff->getPatch()->jsonSerialize());
78+
$this->assertSame(3, $diff->getModifiedCnt());
7879

7980
$original = json_decode($originalJson);
8081
$patch = JsonPatch::import(json_decode($patchJson));

‎tests/src/JsonPatchTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,49 @@ public function testNull()
7272

7373
}
7474

75+
76+
public function testInvalidPatch()
77+
{
78+
$this->setExpectedException(get_class(new Exception()), 'Array expected in JsonPatch::import');
79+
JsonPatch::import(123);
80+
}
81+
82+
public function testMissingOp()
83+
{
84+
$this->setExpectedException(get_class(new Exception()), 'Missing "op" in operation data');
85+
JsonPatch::import(array((object)array('path' => '/123')));
86+
}
87+
88+
public function testMissingPath()
89+
{
90+
$this->setExpectedException(get_class(new Exception()), 'Missing "path" in operation data');
91+
JsonPatch::import(array((object)array('op' => 'wat')));
92+
}
93+
94+
public function testInvalidOp()
95+
{
96+
$this->setExpectedException(get_class(new Exception()), 'Unknown "op": wat');
97+
JsonPatch::import(array((object)array('op' => 'wat', 'path' => '/123')));
98+
}
99+
100+
public function testMissingFrom()
101+
{
102+
$this->setExpectedException(get_class(new Exception()), 'Missing "from" in operation data');
103+
JsonPatch::import(array((object)array('op' => 'copy', 'path' => '/123')));
104+
}
105+
106+
public function testMissingValue()
107+
{
108+
$this->setExpectedException(get_class(new Exception()), 'Missing "value" in operation data');
109+
JsonPatch::import(array(array('op' => 'add', 'path' => '/123')));
110+
}
111+
112+
public function testApply()
113+
{
114+
$p = JsonPatch::import(array(array('op' => 'copy', 'path' => '/1', 'from' => '/0')));
115+
$original = array('AAA');
116+
$p->apply($original);
117+
$this->assertSame(array('AAA', 'AAA'), $original);
118+
}
119+
75120
}

‎tests/src/RearrangeArrayTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
class RearrangeArrayTest extends \PHPUnit_Framework_TestCase
99
{
10+
/**
11+
* @throws \Swaggest\JsonDiff\Exception
12+
*/
1013
public function testRearrangeArray()
1114
{
1215
$oldJson = <<<'JSON'
@@ -125,4 +128,9 @@ public function testRearrangeArray()
125128
$this->assertSame($expectedJson, json_encode($m->getRearranged(), JSON_PRETTY_PRINT));
126129
}
127130

131+
public function testRearrangeNoUnique()
132+
{
133+
134+
}
135+
128136
}

‎tests/src/SpecTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace Swaggest\JsonDiff\Tests;
4+
5+
use Swaggest\JsonDiff\Exception;
6+
use Swaggest\JsonDiff\JsonPatch;
7+
8+
/**
9+
* @see https://github.com/json-patch/json-patch-tests
10+
*/
11+
class SpecTest extends \PHPUnit_Framework_TestCase
12+
{
13+
/**
14+
* @dataProvider specTestsProvider
15+
*/
16+
public function testSpecTests($case)
17+
{
18+
$this->doTest($case);
19+
}
20+
21+
/**
22+
* @dataProvider testsProvider
23+
*/
24+
public function testTests($case)
25+
{
26+
$this->doTest($case);
27+
}
28+
29+
30+
public function testsProvider()
31+
{
32+
return $this->provider(__DIR__ . '/../assets/tests.json');
33+
}
34+
35+
public function specTestsProvider()
36+
{
37+
return $this->provider(__DIR__ . '/../assets/spec-tests.json');
38+
}
39+
40+
protected function provider($path)
41+
{
42+
$cases = json_decode(file_get_contents($path));
43+
44+
$testCases = array();
45+
foreach ($cases as $i => $case) {
46+
if (!isset($case->comment)) {
47+
$comment = 'unknown' . $i;
48+
} else {
49+
$comment = $case->comment;
50+
}
51+
52+
$testCases[$comment] = array(
53+
'case' => $case,
54+
);
55+
}
56+
return $testCases;
57+
}
58+
59+
protected function doTest($case) {
60+
if (isset($case->disabled) && $case->disabled) {
61+
$this->markTestSkipped('test is disabled');
62+
return;
63+
}
64+
65+
if (!is_object($case->doc)) {
66+
$doc = $case->doc;
67+
} else {
68+
$doc = clone $case->doc;
69+
}
70+
$patch = $case->patch;
71+
$hasExpected = array_key_exists('expected', (array)$case);
72+
$expected = isset($case->expected) ? $case->expected : null;
73+
$error = isset($case->error) ? $case->error : null;
74+
$jsonOptions = JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT;
75+
76+
try {
77+
$patch = JsonPatch::import($patch);
78+
$patch->apply($doc);
79+
if ($error !== null) {
80+
$this->fail('Error expected: ' . $error
81+
. "\n" . json_encode($case, $jsonOptions));
82+
}
83+
if ($hasExpected) {
84+
$this->assertEquals($expected, $doc, json_encode($case, $jsonOptions)
85+
. "\n" . json_encode($doc, $jsonOptions));
86+
}
87+
} catch (Exception $e) {
88+
if ($e->getCode() === Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED) {
89+
$this->markTestSkipped('Empty property name unsupported in PHP <7.1');
90+
}
91+
92+
if ($error === null) {
93+
$this->fail($e->getMessage()
94+
. "\n" . json_encode($case, $jsonOptions));
95+
}
96+
}
97+
}
98+
99+
}

0 commit comments

Comments
 (0)
Please sign in to comment.