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
arrayas a return type and suggests a more specific type. This type is inferred from the actual code.