Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | <?php |
||
19 | class ShowcaseControllerTest extends RestTestCase |
||
20 | { |
||
21 | /** |
||
22 | * @const complete content type string expected on a resouce |
||
23 | */ |
||
24 | const CONTENT_TYPE = 'application/json; charset=UTF-8; profile=http://localhost/schema/hans/showcase/item'; |
||
25 | |||
26 | /** |
||
27 | * @const corresponding vendorized schema mime type |
||
28 | */ |
||
29 | const COLLECTION_TYPE = 'application/json; charset=UTF-8; profile=http://localhost/schema/hans/showcase/collection'; |
||
30 | |||
31 | /** |
||
32 | * suppress setup client and load fixtures of parent class |
||
33 | * |
||
34 | * @return void |
||
35 | */ |
||
36 | public function setUp() |
||
37 | { |
||
38 | $this->loadFixtures( |
||
39 | [ |
||
40 | LoadLanguageData::class |
||
41 | ], |
||
42 | null, |
||
43 | 'doctrine_mongodb' |
||
44 | ); |
||
45 | } |
||
46 | |||
47 | /** |
||
48 | * checks empty objects |
||
49 | * |
||
50 | * @return void |
||
51 | */ |
||
52 | public function testGetEmptyObject() |
||
91 | |||
92 | /** |
||
93 | * see how our missing fields are explained to us |
||
94 | * |
||
95 | * @return void |
||
96 | */ |
||
97 | public function testMissingFields() |
||
98 | { |
||
99 | $document = json_decode( |
||
100 | file_get_contents(dirname(__FILE__).'/../resources/showcase-incomplete.json'), |
||
101 | true |
||
102 | ); |
||
103 | |||
104 | $client = static::createRestClient(); |
||
105 | $client->post('/hans/showcase', $document); |
||
106 | |||
107 | $expectedErrors = []; |
||
108 | $notNullError = new \stdClass(); |
||
109 | $notNullError->propertyPath = 'aBoolean'; |
||
110 | $notNullError->message = 'The property aBoolean is required'; |
||
111 | $expectedErrors[] = $notNullError; |
||
112 | // test choices field (string should not be blank) |
||
113 | $notNullErrorChoices = new \stdClass(); |
||
114 | $notNullErrorChoices->propertyPath = 'choices'; |
||
115 | $notNullErrorChoices->message = 'The property choices is required'; |
||
116 | $expectedErrors[] = $notNullErrorChoices; |
||
117 | |||
118 | $this->assertJsonStringEqualsJsonString( |
||
119 | json_encode($expectedErrors), |
||
120 | json_encode($client->getResults()) |
||
121 | ); |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * see how our empty fields are explained to us |
||
126 | * |
||
127 | * @return void |
||
128 | */ |
||
129 | public function testEmptyAllFields() |
||
130 | { |
||
131 | $document = [ |
||
132 | 'anotherInt' => 6555488894525, |
||
133 | 'testField' => ['en' => 'a test string'], |
||
134 | 'aBoolean' => '', |
||
135 | 'contactCode' => [ |
||
136 | 'text' => ['en' => 'Some Text'], |
||
137 | 'someDate' => '1984-05-01T00:00:00+0000', |
||
138 | ], |
||
139 | 'contact' => [ |
||
140 | 'type' => '', |
||
141 | 'value' => '', |
||
142 | 'protocol' => '', |
||
143 | ], |
||
144 | ]; |
||
145 | |||
146 | $client = static::createRestClient(); |
||
147 | $client->post('/hans/showcase', $document); |
||
148 | |||
149 | $this->assertEquals( |
||
150 | Response::HTTP_BAD_REQUEST, |
||
151 | $client->getResponse()->getStatusCode() |
||
152 | ); |
||
153 | |||
154 | $this->assertEquals( |
||
155 | [ |
||
156 | (object) [ |
||
157 | 'propertyPath' => 'choices', |
||
158 | 'message' => 'The property choices is required', |
||
159 | ], |
||
160 | (object) [ |
||
161 | 'propertyPath' => 'aBoolean', |
||
162 | 'message' => 'String value found, but a boolean is required', |
||
163 | ], |
||
164 | (object) [ |
||
165 | 'propertyPath' => 'contact.type', |
||
166 | 'message' => 'Must be at least 1 characters long', |
||
167 | ], |
||
168 | (object) [ |
||
169 | 'propertyPath' => 'contact.protocol', |
||
170 | 'message' => 'Must be at least 1 characters long', |
||
171 | ], |
||
172 | (object) [ |
||
173 | 'propertyPath' => 'contact.value', |
||
174 | 'message' => 'Must be at least 1 characters long', |
||
175 | ] |
||
176 | ], |
||
177 | $client->getResults() |
||
178 | ); |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * see how our empty fields are explained to us |
||
183 | * |
||
184 | * @return void |
||
185 | */ |
||
186 | public function testEmptyFields() |
||
187 | { |
||
188 | $document = [ |
||
189 | 'anotherInt' => 6555488894525, |
||
190 | 'testField' => ['en' => 'a test string'], |
||
191 | 'aBoolean' => true, |
||
192 | 'contactCode' => [ |
||
193 | 'text' => ['en' => 'Some Text'], |
||
194 | 'someDate' => '1984-05-01T00:00:00+0000', |
||
195 | ], |
||
196 | 'contact' => [ |
||
197 | 'type' => 'abc', |
||
198 | 'value' => '', |
||
199 | 'protocol' => '', |
||
200 | ], |
||
201 | ]; |
||
202 | |||
203 | $client = static::createRestClient(); |
||
204 | $client->post('/hans/showcase', $document); |
||
205 | |||
206 | $this->assertEquals( |
||
207 | Response::HTTP_BAD_REQUEST, |
||
208 | $client->getResponse()->getStatusCode() |
||
209 | ); |
||
210 | |||
211 | $this->assertEquals( |
||
212 | [ |
||
213 | (object) [ |
||
214 | 'propertyPath' => 'choices', |
||
215 | 'message' => 'The property choices is required', |
||
216 | ], |
||
217 | (object) [ |
||
218 | 'propertyPath' => 'contact.protocol', |
||
219 | 'message' => 'Must be at least 1 characters long', |
||
220 | ], |
||
221 | (object) [ |
||
222 | 'propertyPath' => 'contact.value', |
||
223 | 'message' => 'Must be at least 1 characters long', |
||
224 | ], |
||
225 | ], |
||
226 | $client->getResults() |
||
227 | ); |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * make sure an invalid choice value is detected |
||
232 | * |
||
233 | * @return void |
||
234 | */ |
||
235 | public function testWrongChoiceValue() |
||
236 | { |
||
237 | $payload = json_decode(file_get_contents($this->postCreationDataProvider()['minimal'][0])); |
||
238 | $payload->choices = 'invalidChoice'; |
||
239 | |||
240 | $client = static::createRestClient(); |
||
241 | $client->post('/hans/showcase', $payload); |
||
242 | $this->assertEquals(400, $client->getResponse()->getStatusCode()); |
||
243 | |||
244 | $expectedErrors = []; |
||
245 | $expectedErrors[0] = new \stdClass(); |
||
246 | $expectedErrors[0]->propertyPath = "choices"; |
||
247 | $expectedErrors[0]->message = 'Does not have a value in the enumeration ["<",">","=",">=","<=","<>"]'; |
||
248 | |||
249 | $this->assertJsonStringEqualsJsonString( |
||
250 | json_encode($expectedErrors), |
||
251 | json_encode($client->getResults()) |
||
252 | ); |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * make sure an invalid extref value is detected |
||
257 | * |
||
258 | * @return void |
||
259 | */ |
||
260 | public function testWrongExtRef() |
||
261 | { |
||
262 | $payload = json_decode(file_get_contents($this->postCreationDataProvider()['minimal'][0])); |
||
263 | $payload->nestedApps = [ |
||
264 | (object) ['$ref' => 'http://localhost/core/module/name'], |
||
265 | (object) ['$ref' => 'unknown'] |
||
266 | ]; |
||
267 | |||
268 | $client = static::createRestClient(); |
||
269 | $client->post('/hans/showcase', $payload); |
||
270 | $this->assertEquals(400, $client->getResponse()->getStatusCode()); |
||
271 | |||
272 | $expectedErrors = [ |
||
273 | (object) [ |
||
274 | 'propertyPath' => "nestedApps[0].\$ref", |
||
275 | 'message' => |
||
276 | 'Value "http://localhost/core/module/name" does not refer to a correct collection for this extref.' |
||
277 | ], |
||
278 | (object) [ |
||
279 | 'propertyPath' => "nestedApps[1].\$ref", |
||
280 | 'message' => |
||
281 | 'Does not match the regex pattern (\/core\/app\/)([a-zA-Z0-9\-_\+\040\'\.]+)$' |
||
282 | ] |
||
283 | ]; |
||
284 | |||
285 | $this->assertJsonStringEqualsJsonString( |
||
286 | json_encode($expectedErrors), |
||
287 | json_encode($client->getResults()) |
||
288 | ); |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * insert various formats to see if all works as expected |
||
293 | * |
||
294 | * @dataProvider postCreationDataProvider |
||
295 | * |
||
296 | * @param string $filename filename |
||
297 | * |
||
298 | * @return void |
||
299 | */ |
||
300 | public function testPost($filename) |
||
329 | |||
330 | /** |
||
331 | * Provides test sets for the testPost() test. |
||
332 | * |
||
333 | * @return array |
||
|
|||
334 | */ |
||
335 | public function postCreationDataProvider() |
||
344 | |||
345 | /** |
||
346 | * test if we can save & retrieve extrefs inside 'free form objects' |
||
347 | * |
||
348 | * @return void |
||
349 | */ |
||
350 | public function testFreeFormExtRefs() |
||
390 | |||
391 | /** |
||
392 | * are extra fields denied? |
||
393 | * |
||
394 | * @return void |
||
395 | */ |
||
396 | public function testExtraFieldPost() |
||
425 | |||
426 | /** |
||
427 | * Test RQL select statement |
||
428 | * |
||
429 | * @return void |
||
430 | */ |
||
431 | public function testRqlSelect() |
||
432 | { |
||
433 | $this->loadFixtures( |
||
434 | ['GravitonDyn\ShowCaseBundle\DataFixtures\MongoDB\LoadShowCaseData'], |
||
435 | null, |
||
436 | 'doctrine_mongodb' |
||
437 | ); |
||
438 | |||
439 | $filtred = json_decode( |
||
440 | file_get_contents(dirname(__FILE__).'/../resources/showcase-rql-select-filtred.json'), |
||
441 | false |
||
442 | ); |
||
443 | |||
444 | $fields = [ |
||
445 | 'someFloatyDouble', |
||
446 | 'contact', |
||
447 | 'contactCode.text', |
||
448 | 'unstructuredObject.booleanField', |
||
449 | 'unstructuredObject.hashField.someField', |
||
450 | 'unstructuredObject.nestedArrayField.anotherField', |
||
451 | 'nestedCustomers', |
||
452 | 'choices' |
||
453 | ]; |
||
454 | $rqlSelect = 'select('.implode(',', array_map([$this, 'encodeRqlString'], $fields)).')'; |
||
455 | |||
456 | $client = static::createRestClient(); |
||
457 | $client->request('GET', '/hans/showcase/?'.$rqlSelect); |
||
458 | |||
459 | $this->assertEquals($filtred, $client->getResults()); |
||
460 | |||
461 | foreach ([ |
||
462 | '500' => $filtred[0], |
||
463 | '600' => $filtred[1], |
||
464 | ] as $id => $item) { |
||
465 | $client = static::createRestClient(); |
||
466 | $client->request('GET', '/hans/showcase/'.$id.'?'.$rqlSelect); |
||
467 | $this->assertEquals($item, $client->getResults()); |
||
468 | } |
||
469 | } |
||
470 | |||
471 | /** |
||
472 | * Test to see if we can do like() searches on identifier fields |
||
473 | * |
||
474 | * @return void |
||
475 | */ |
||
476 | public function testLikeSearchOnIdentifierField() |
||
477 | { |
||
478 | // Load fixtures |
||
479 | $this->loadFixtures( |
||
480 | ['GravitonDyn\ShowCaseBundle\DataFixtures\MongoDB\LoadShowCaseData'], |
||
481 | null, |
||
482 | 'doctrine_mongodb' |
||
483 | ); |
||
484 | |||
485 | $client = static::createRestClient(); |
||
486 | $client->request('GET', '/hans/showcase/?like(id,5*)'); |
||
487 | |||
488 | // we should only get 1 ;-) |
||
489 | $this->assertEquals(1, count($client->getResults())); |
||
490 | |||
491 | $client = static::createRestClient(); |
||
492 | $client->request('GET', '/hans/showcase/?like(id,*0)'); |
||
493 | |||
494 | // this should get both |
||
495 | $this->assertEquals(2, count($client->getResults())); |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Test PATCH for deep nested attribute |
||
500 | * |
||
501 | * @return void |
||
502 | */ |
||
503 | View Code Duplication | public function testPatchDeepNestedProperty() |
|
536 | |||
537 | /** |
||
538 | * Test success PATCH method - response headers contains link to resource |
||
539 | * |
||
540 | * @return void |
||
541 | */ |
||
542 | public function testPatchSuccessResponseHeaderContainsResourceLink() |
||
570 | |||
571 | /** |
||
572 | * Test PATCH method - remove/change ID not allowed |
||
573 | * |
||
574 | * @return void |
||
575 | */ |
||
576 | public function testPatchRemoveAndChangeIdNotAllowed() |
||
598 | |||
599 | /** |
||
600 | * Test PATCH: add property to free object structure |
||
601 | * |
||
602 | * @return void |
||
603 | */ |
||
604 | View Code Duplication | public function testPatchAddPropertyToFreeObject() |
|
637 | |||
638 | /** |
||
639 | * Test PATCH for $ref attribute |
||
640 | * |
||
641 | * @return void |
||
642 | * @incomplete |
||
643 | */ |
||
644 | public function testApplyPatchForRefAttribute() |
||
645 | { |
||
646 | // Load fixtures |
||
647 | $this->loadFixtures( |
||
648 | [ |
||
649 | 'GravitonDyn\ShowCaseBundle\DataFixtures\MongoDB\LoadShowCaseData' |
||
650 | ], |
||
651 | null, |
||
652 | 'doctrine_mongodb' |
||
653 | ); |
||
654 | |||
655 | // Apply PATCH request |
||
656 | $client = static::createRestClient(); |
||
657 | $patchJson = json_encode( |
||
658 | [ |
||
659 | [ |
||
660 | 'op' => 'replace', |
||
661 | 'path' => '/nestedApps/0', |
||
662 | 'value' => [ |
||
663 | '$ref' => 'http://localhost/core/app/admin' |
||
664 | ] |
||
665 | ] |
||
666 | ] |
||
667 | ); |
||
668 | $client->request('PATCH', '/hans/showcase/500', array(), array(), array(), $patchJson); |
||
669 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); |
||
670 | |||
671 | // Check patched result |
||
672 | $client = static::createRestClient(); |
||
673 | $client->request('GET', '/hans/showcase/500'); |
||
674 | |||
675 | $result = $client->getResults(); |
||
676 | $this->assertEquals( |
||
677 | 'http://localhost/core/app/admin', |
||
678 | $result->nestedApps[0]->{'$ref'} |
||
679 | ); |
||
680 | } |
||
681 | |||
682 | /** |
||
683 | * Test PATCH: apply patch which results to invalid Showcase schema |
||
684 | * |
||
685 | * @return void |
||
686 | */ |
||
687 | public function testPatchToInvalidShowcase() |
||
716 | |||
717 | /** |
||
718 | * Test PATCH: remove element from array |
||
719 | * |
||
720 | * @return void |
||
721 | */ |
||
722 | View Code Duplication | public function testRemoveFromArrayPatch() |
|
752 | |||
753 | /** |
||
754 | * Test PATCH: add new element to array |
||
755 | * |
||
756 | * @return void |
||
757 | */ |
||
758 | public function testAddElementToSpecificIndexInArrayPatch() |
||
793 | |||
794 | /** |
||
795 | * Test PATCH: add complex object App to array |
||
796 | * |
||
797 | * @group ref |
||
798 | * @return void |
||
799 | */ |
||
800 | View Code Duplication | public function testPatchAddComplexObjectToSpecificIndexInArray() |
|
835 | |||
836 | /** |
||
837 | * Test PATCH: add complex object App to array |
||
838 | * |
||
839 | * @group ref |
||
840 | * @return void |
||
841 | */ |
||
842 | View Code Duplication | public function testPatchAddComplexObjectToTheEndOfArray() |
|
877 | |||
878 | /** |
||
879 | * Test PATCH: test operation to undefined index |
||
880 | * |
||
881 | * @group ref |
||
882 | * @return void |
||
883 | */ |
||
884 | public function testPatchTestOperationToUndefinedIndexThrowsException() |
||
908 | |||
909 | /** |
||
910 | * Test PATCH: add complex object App to array |
||
911 | * |
||
912 | * @group ref |
||
913 | * @return void |
||
914 | */ |
||
915 | public function testPatchAddElementToUndefinedIndexResponseAsBadRequest() |
||
946 | |||
947 | /** |
||
948 | * Encode string value in RQL |
||
949 | * |
||
950 | * @param string $value String value |
||
951 | * @return string |
||
952 | */ |
||
953 | View Code Duplication | private function encodeRqlString($value) |
|
965 | |||
966 | /** |
||
967 | * Trigger a 301 Status code |
||
968 | * |
||
969 | * @param string $url requested url |
||
970 | * @param string $redirectUrl redirected url |
||
971 | * @dataProvider rqlDataProvider |
||
972 | * @return void |
||
973 | */ |
||
974 | View Code Duplication | public function testTrigger301($url, $redirectUrl) |
|
975 | { |
||
976 | $client = static::createRestClient(); |
||
977 | $client->request('GET', $url); |
||
978 | $this->assertEquals(301, $client->getResponse()->getStatusCode()); |
||
979 | $this->assertEquals($redirectUrl, $client->getResponse()->headers->get('Location')); |
||
980 | } |
||
981 | |||
982 | /** |
||
983 | * Provides urls for the testTrigger301() test. |
||
984 | * |
||
985 | * @return array |
||
986 | */ |
||
987 | public function rqlDataProvider() |
||
994 | |||
995 | /** |
||
996 | * Here we test the client expectation in "id" property exposing in the json schema. |
||
997 | * |
||
998 | * They want |
||
999 | * * "id" of an extref object should *not* be described/present in schema |
||
1000 | * * "id" of others, including embedded objects, *should* be described/present in schema |
||
1001 | * |
||
1002 | * @return void |
||
1003 | */ |
||
1004 | public function testCorrectIdExposingInSchema() |
||
1021 | |||
1022 | /** |
||
1023 | * test finding of showcases by ref |
||
1024 | * |
||
1025 | * @dataProvider findByExtrefProvider |
||
1026 | * |
||
1027 | * @param string $field which reference to search in |
||
1028 | * @param mixed $url ref to search for |
||
1029 | * @param integer $count number of results to expect |
||
1030 | * |
||
1031 | * @return void |
||
1032 | */ |
||
1033 | public function testFindByExtref($field, $url, $count) |
||
1052 | |||
1053 | /** |
||
1054 | * @return array |
||
1055 | */ |
||
1056 | View Code Duplication | public function findByExtrefProvider() |
|
1081 | } |
||
1082 |
This check looks for the generic type
array
as a return type and suggests a more specific type. This type is inferred from the actual code.