set('alternate_base_url', $this->baseURI); Security::setCurrentUser(null); } public function testApiAccess() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $page1 = $this->objFromFixture(RestfulServerTestPage::class, 'page1'); // normal GET should succeed with $api_access enabled $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(200, $response->getStatusCode()); $_SERVER['PHP_AUTH_USER'] = 'user@test.com'; $_SERVER['PHP_AUTH_PW'] = 'user'; // even with logged in user a GET with $api_access disabled should fail $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestPage::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $page1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(401, $response->getStatusCode()); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testApiAccessBoolean() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertContains('', $response->getBody()); $this->assertContains('', $response->getBody()); $this->assertContains('', $response->getBody()); $this->assertContains('getBody()); $this->assertContains('getBody()); } public function testAuthenticatedGET() { $thing1 = $this->objFromFixture(RestfulServerTestSecretThing::class, 'thing1'); $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); // @todo create additional mock object with authenticated VIEW permissions $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestSecretThing::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $thing1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(401, $response->getStatusCode()); $_SERVER['PHP_AUTH_USER'] = 'user@test.com'; $_SERVER['PHP_AUTH_PW'] = 'user'; $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(200, $response->getStatusCode()); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testAuthenticatedPUT() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $data = array('Comment' => 'created'); $response = Director::test($url, $data, null, 'PUT'); $this->assertEquals(401, $response->getStatusCode()); // Permission failure $_SERVER['PHP_AUTH_USER'] = 'editor@test.com'; $_SERVER['PHP_AUTH_PW'] = 'editor'; $response = Director::test($url, $data, null, 'PUT'); $this->assertEquals(200, $response->getStatusCode()); // Success unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testGETRelationshipsXML() { $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1'); $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1'); $rating2 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating2'); // @todo should be set up by fixtures, doesn't work for some reason... $author1->Ratings()->add($rating1); $author1->Ratings()->add($rating2); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(200, $response->getStatusCode()); $responseArr = Convert::xml2array($response->getBody()); $xmlTagSafeClassName = $this->urlSafeClassname(RestfulServerTestAuthorRating::class); $ratingsArr = $responseArr['Ratings'][$xmlTagSafeClassName]; $this->assertEquals(2, count($ratingsArr)); $ratingIDs = array( (int)$ratingsArr[0]['@attributes']['id'], (int)$ratingsArr[1]['@attributes']['id'] ); $this->assertContains($rating1->ID, $ratingIDs); $this->assertContains($rating2->ID, $ratingIDs); } public function testGETManyManyRelationshipsXML() { // author4 has related authors author2 and author3 $author2 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author2'); $author3 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author3'); $author4 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author4'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author4->ID . '/RelatedAuthors'; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(200, $response->getStatusCode()); $arr = Convert::xml2array($response->getBody()); $xmlSafeClassName = $this->urlSafeClassname(RestfulServerTestAuthor::class); $authorsArr = $arr[$xmlSafeClassName]; $this->assertEquals(2, count($authorsArr)); $ratingIDs = array( (int)$authorsArr[0]['ID'], (int)$authorsArr[1]['ID'] ); $this->assertContains($author2->ID, $ratingIDs); $this->assertContains($author3->ID, $ratingIDs); } public function testPUTWithFormEncoded() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $_SERVER['PHP_AUTH_USER'] = 'editor@test.com'; $_SERVER['PHP_AUTH_PW'] = 'editor'; $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $body = 'Name=Updated Comment&Comment=updated'; $headers = array( 'Content-Type' => 'application/x-www-form-urlencoded' ); $response = Director::test($url, null, null, 'PUT', $body, $headers); $this->assertEquals(200, $response->getStatusCode()); // Success // Assumption: XML is default output $responseArr = Convert::xml2array($response->getBody()); $this->assertEquals($comment1->ID, $responseArr['ID']); $this->assertEquals('updated', $responseArr['Comment']); $this->assertEquals('Updated Comment', $responseArr['Name']); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testPOSTWithFormEncoded() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $_SERVER['PHP_AUTH_USER'] = 'editor@test.com'; $_SERVER['PHP_AUTH_PW'] = 'editor'; $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname"; $body = 'Name=New Comment&Comment=created'; $headers = array( 'Content-Type' => 'application/x-www-form-urlencoded' ); $response = Director::test($url, null, null, 'POST', $body, $headers); $this->assertEquals(201, $response->getStatusCode()); // Created // Assumption: XML is default output $responseArr = Convert::xml2array($response->getBody()); $this->assertTrue($responseArr['ID'] > 0); $this->assertNotEquals($responseArr['ID'], $comment1->ID); $this->assertEquals('created', $responseArr['Comment']); $this->assertEquals('New Comment', $responseArr['Name']); $this->assertEquals( Controller::join_links($url, $responseArr['ID'] . '.xml'), $response->getHeader('Location') ); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testPostWithoutBodyReturnsNoContent() { $_SERVER['PHP_AUTH_USER'] = 'editor@test.com'; $_SERVER['PHP_AUTH_PW'] = 'editor'; $url = "{$this->baseURI}/api/v1/" . RestfulServerTestComment::class; $response = Director::test($url, null, null, 'POST'); $this->assertEquals('No Content', $response->getBody()); unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); } public function testPUTwithJSON() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $_SERVER['PHP_AUTH_USER'] = 'editor@test.com'; $_SERVER['PHP_AUTH_PW'] = 'editor'; // by acceptance mimetype $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $body = '{"Comment":"updated"}'; $response = Director::test($url, null, null, 'PUT', $body, array( 'Content-Type'=>'application/json', 'Accept' => 'application/json' )); $this->assertEquals(200, $response->getStatusCode()); // Updated $obj = Convert::json2obj($response->getBody()); $this->assertEquals($comment1->ID, $obj->ID); $this->assertEquals('updated', $obj->Comment); // by extension $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/{$comment1->ID}.json"; $body = '{"Comment":"updated"}'; $response = Director::test($url, null, null, 'PUT', $body); $this->assertEquals(200, $response->getStatusCode()); // Updated $this->assertEquals($url, $response->getHeader('Location')); $obj = Convert::json2obj($response->getBody()); $this->assertEquals($comment1->ID, $obj->ID); $this->assertEquals('updated', $obj->Comment); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testPUTwithXML() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $_SERVER['PHP_AUTH_USER'] = 'editor@test.com'; $_SERVER['PHP_AUTH_PW'] = 'editor'; // by mimetype $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $body = 'updated'; $response = Director::test($url, null, null, 'PUT', $body, array('Content-Type'=>'text/xml')); $this->assertEquals(200, $response->getStatusCode()); // Updated $obj = Convert::xml2array($response->getBody()); $this->assertEquals($comment1->ID, $obj['ID']); $this->assertEquals('updated', $obj['Comment']); // by extension $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/{$comment1->ID}.xml"; $body = 'updated'; $response = Director::test($url, null, null, 'PUT', $body); $this->assertEquals(200, $response->getStatusCode()); // Updated $this->assertEquals($url, $response->getHeader('Location')); $obj = Convert::xml2array($response->getBody()); $this->assertEquals($comment1->ID, $obj['ID']); $this->assertEquals('updated', $obj['Comment']); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testHTTPAcceptAndContentType() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $headers = array('Accept' => 'application/json'); $response = Director::test($url, null, null, 'GET', null, $headers); $this->assertEquals(200, $response->getStatusCode()); // Success $obj = Convert::json2obj($response->getBody()); $this->assertEquals($comment1->ID, $obj->ID); $this->assertEquals('application/json', $response->getHeader('Content-Type')); } public function testNotFound() { $_SERVER['PHP_AUTH_USER'] = 'user@test.com'; $_SERVER['PHP_AUTH_PW'] = 'user'; $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/99"; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(404, $response->getStatusCode()); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testMethodNotAllowed() { $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID; $response = Director::test($url, null, null, 'UNKNOWNHTTPMETHOD'); $this->assertEquals(405, $response->getStatusCode()); } public function testConflictOnExistingResourceWhenUsingPost() { $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID; $response = Director::test($url, null, null, 'POST'); $this->assertEquals(409, $response->getStatusCode()); } public function testUnsupportedMediaType() { $_SERVER['PHP_AUTH_USER'] = 'user@test.com'; $_SERVER['PHP_AUTH_PW'] = 'user'; $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname"; $data = "Comment||\/||updated"; // weird format $headers = array('Content-Type' => 'text/weirdformat'); $response = Director::test($url, null, null, 'POST', $data, $headers); $this->assertEquals(415, $response->getStatusCode()); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } public function testXMLValueFormatting() { $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertContains('' . $rating1->ID . '', $response->getBody()); $this->assertContains('' . $rating1->Rating . '', $response->getBody()); } public function testApiAccessFieldRestrictions() { $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1'); $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertContains('', $response->getBody()); $this->assertContains('', $response->getBody()); $this->assertContains('getBody()); $this->assertNotContains('', $response->getBody()); $this->assertNotContains('', $response->getBody()); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID . '?add_fields=SecretField,SecretRelation'; $response = Director::test($url, null, null, 'GET'); $this->assertNotContains( '', $response->getBody(), '"add_fields" URL parameter filters out disallowed fields from $api_access' ); $this->assertNotContains( '', $response->getBody(), '"add_fields" URL parameter filters out disallowed relations from $api_access' ); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID . '?fields=SecretField,SecretRelation'; $response = Director::test($url, null, null, 'GET'); $this->assertNotContains( '', $response->getBody(), '"fields" URL parameter filters out disallowed fields from $api_access' ); $this->assertNotContains( '', $response->getBody(), '"fields" URL parameter filters out disallowed relations from $api_access' ); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . '/Ratings'; $response = Director::test($url, null, null, 'GET'); $this->assertContains( '', $response->getBody(), 'Relation viewer shows fields allowed through $api_access' ); $this->assertNotContains( '', $response->getBody(), 'Relation viewer on has-many filters out disallowed fields from $api_access' ); } public function testApiAccessRelationRestrictionsInline() { $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID; $response = Director::test($url, null, null, 'GET'); $this->assertNotContains('getBody(), 'Restricts many-many with api_access=false'); $this->assertNotContains('getBody(), 'Restricts has-many with api_access=false'); } public function testApiAccessRelationRestrictionsOnEndpoint() { $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . "/ProfilePage"; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(404, $response->getStatusCode(), 'Restricts has-one with api_access=false'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . "/RelatedPages"; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(404, $response->getStatusCode(), 'Restricts many-many with api_access=false'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . "/PublishedPages"; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(404, $response->getStatusCode(), 'Restricts has-many with api_access=false'); } public function testApiAccessWithPUT() { $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1'); $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID; $data = array( 'Rating' => '42', 'WriteProtectedField' => 'haxx0red' ); $response = Director::test($url, $data, null, 'PUT'); // Assumption: XML is default output $responseArr = Convert::xml2array($response->getBody()); $this->assertEquals(42, $responseArr['Rating']); $this->assertNotEquals('haxx0red', $responseArr['WriteProtectedField']); } public function testJSONDataFormatter() { $formatter = new JSONDataFormatter(); $editor = $this->objFromFixture(Member::class, 'editor'); $user = $this->objFromFixture(Member::class, 'user'); // The DataFormatter performs canView calls // these are `Member`s so we need to be ADMIN types $this->logInWithPermission('ADMIN'); $this->assertEquals( '{"FirstName":"Editor","Email":"editor@test.com"}', $formatter->convertDataObject($editor, ["FirstName", "Email"]), "Correct JSON formatting with field subset" ); $set = Member::get() ->filter('ID', [$editor->ID, $user->ID]) ->sort('"Email" ASC'); // for sorting for postgres $this->assertEquals( '{"totalSize":null,"items":[{"FirstName":"Editor","Email":"editor@test.com"},' . '{"FirstName":"User","Email":"user@test.com"}]}', $formatter->convertDataObjectSet($set, ["FirstName", "Email"]), "Correct JSON formatting on a dataobjectset with field filter" ); } public function testApiAccessWithPOST() { $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/"; $data = [ 'Rating' => '42', 'WriteProtectedField' => 'haxx0red' ]; $response = Director::test($url, $data, null, 'POST'); // Assumption: XML is default output $responseArr = Convert::xml2array($response->getBody()); $this->assertEquals(42, $responseArr['Rating']); $this->assertNotEquals('haxx0red', $responseArr['WriteProtectedField']); } public function testCanViewRespectedInList() { // Default content type $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestSecretThing::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/"; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(200, $response->getStatusCode()); $this->assertNotContains('Unspeakable', $response->getBody()); // JSON content type $url = "{$this->baseURI}/api/v1/$urlSafeClassname.json"; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(200, $response->getStatusCode()); $this->assertNotContains('Unspeakable', $response->getBody()); $responseArray = Convert::json2array($response->getBody()); $this->assertSame(0, $responseArray['totalSize']); // With authentication $_SERVER['PHP_AUTH_USER'] = 'editor@test.com'; $_SERVER['PHP_AUTH_PW'] = 'editor'; $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestSecretThing::class); $url = "{$this->baseURI}/api/v1/$urlSafeClassname/"; $response = Director::test($url, null, null, 'GET'); $this->assertEquals(200, $response->getStatusCode()); $this->assertContains('Unspeakable', $response->getBody()); // Assumption: default formatter is XML $responseArray = Convert::xml2array($response->getBody()); $this->assertEquals(1, $responseArray['@attributes']['totalSize']); unset($_SERVER['PHP_AUTH_USER']); unset($_SERVER['PHP_AUTH_PW']); } }