Passed
Push — master ( 284ace...71865f )
by Robbie
03:30
created

RestfulServerTest::testGetWithSortAscending()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\RestfulServer\Tests;
4
5
use SilverStripe\RestfulServer\RestfulServer;
6
use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestComment;
7
use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestExceptionThrown;
8
use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestSecretThing;
9
use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestPage;
10
use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestAuthor;
11
use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestAuthorRating;
12
use SilverStripe\Control\Director;
13
use SilverStripe\Core\Convert;
14
use SilverStripe\Control\Controller;
15
use SilverStripe\RestfulServer\Tests\Stubs\RestfulServerTestValidationFailure;
16
use SilverStripe\Security\Member;
17
use SilverStripe\Security\Security;
18
use SilverStripe\ORM\DataObject;
19
use SilverStripe\Dev\SapphireTest;
20
use SilverStripe\RestfulServer\DataFormatter\JSONDataFormatter;
21
use Page;
0 ignored issues
show
Bug introduced by
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use SilverStripe\Core\Config\Config;
23
24
/**
25
 *
26
 * @todo Test Relation getters
27
 * @todo Test filter and limit through GET params
28
 * @todo Test DELETE verb
29
 *
30
 */
31
class RestfulServerTest extends SapphireTest
32
{
33
    protected static $fixture_file = 'RestfulServerTest.yml';
34
35
    protected $baseURI = 'http://www.fakesite.test';
36
37
    protected static $extra_dataobjects = [
38
        RestfulServerTestComment::class,
39
        RestfulServerTestSecretThing::class,
40
        RestfulServerTestPage::class,
41
        RestfulServerTestAuthor::class,
42
        RestfulServerTestAuthorRating::class,
43
        RestfulServerTestValidationFailure::class,
44
        RestfulServerTestExceptionThrown::class,
45
    ];
46
47
    protected function urlSafeClassname($classname)
48
    {
49
        return str_replace('\\', '-', $classname);
50
    }
51
52
    protected function setUp()
53
    {
54
        parent::setUp();
55
        Director::config()->set('alternate_base_url', $this->baseURI);
56
        $this->logOut();
57
    }
58
59
    public function testApiAccess()
60
    {
61
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
62
        $page1 = $this->objFromFixture(RestfulServerTestPage::class, 'page1');
63
64
        // normal GET should succeed with $api_access enabled
65
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
66
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
67
68
        $response = Director::test($url, null, null, 'GET');
69
        $this->assertEquals(200, $response->getStatusCode());
70
71
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
72
        $_SERVER['PHP_AUTH_PW'] = 'user';
73
74
        // even with logged in user a GET with $api_access disabled should fail
75
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestPage::class);
76
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $page1->ID;
77
        $response = Director::test($url, null, null, 'GET');
78
        $this->assertEquals(401, $response->getStatusCode());
79
80
        unset($_SERVER['PHP_AUTH_USER']);
81
        unset($_SERVER['PHP_AUTH_PW']);
82
    }
83
84
    public function testApiAccessBoolean()
85
    {
86
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
87
88
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
89
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
90
        $response = Director::test($url, null, null, 'GET');
91
        $this->assertContains('<ID>', $response->getBody());
92
        $this->assertContains('<Name>', $response->getBody());
93
        $this->assertContains('<Comment>', $response->getBody());
94
        $this->assertContains('<Page', $response->getBody());
95
        $this->assertContains('<Author', $response->getBody());
96
    }
97
98
    public function testAuthenticatedGET()
99
    {
100
        $thing1 = $this->objFromFixture(RestfulServerTestSecretThing::class, 'thing1');
101
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
102
103
        // @todo create additional mock object with authenticated VIEW permissions
104
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestSecretThing::class);
105
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $thing1->ID;
106
        $response = Director::test($url, null, null, 'GET');
107
        $this->assertEquals(401, $response->getStatusCode());
108
109
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
110
        $_SERVER['PHP_AUTH_PW'] = 'user';
111
112
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
113
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
114
        $response = Director::test($url, null, null, 'GET');
115
        $this->assertEquals(200, $response->getStatusCode());
116
117
        unset($_SERVER['PHP_AUTH_USER']);
118
        unset($_SERVER['PHP_AUTH_PW']);
119
    }
120
121
    public function testGETWithFieldAlias()
122
    {
123
        Config::inst()->set(RestfulServerTestAuthorRating::class, 'api_field_mapping', ['rate' => 'Rating']);
0 ignored issues
show
Bug introduced by
The method set() does not exist on SilverStripe\Config\Coll...nfigCollectionInterface. It seems like you code against a sub-type of SilverStripe\Config\Coll...nfigCollectionInterface such as SilverStripe\Config\Coll...nfigCollectionInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

123
        Config::inst()->/** @scrutinizer ignore-call */ set(RestfulServerTestAuthorRating::class, 'api_field_mapping', ['rate' => 'Rating']);
Loading history...
124
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
125
126
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
127
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID;
128
        $response = Director::test($url, null, null, 'GET');
129
        $responseArr = Convert::xml2array($response->getBody());
130
        $this->assertEquals(3, $responseArr['rate']);
131
    }
132
133
    public function testAuthenticatedPUT()
134
    {
135
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
136
137
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
138
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
139
        $data = array('Comment' => 'created');
140
141
        $response = Director::test($url, $data, null, 'PUT');
142
        $this->assertEquals(401, $response->getStatusCode()); // Permission failure
143
144
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
145
        $_SERVER['PHP_AUTH_PW'] = 'editor';
146
        $response = Director::test($url, $data, null, 'PUT');
147
        $this->assertEquals(202, $response->getStatusCode()); // Accepted
148
149
        unset($_SERVER['PHP_AUTH_USER']);
150
        unset($_SERVER['PHP_AUTH_PW']);
151
    }
152
153
    public function testGETRelationshipsXML()
154
    {
155
        $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1');
156
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
157
        $rating2 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating2');
158
159
        // @todo should be set up by fixtures, doesn't work for some reason...
160
        $author1->Ratings()->add($rating1);
161
        $author1->Ratings()->add($rating2);
162
163
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
164
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID;
165
        $response = Director::test($url, null, null, 'GET');
166
        $this->assertEquals(200, $response->getStatusCode());
167
168
        $responseArr = Convert::xml2array($response->getBody());
169
        $xmlTagSafeClassName = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
170
        $ratingsArr = $responseArr['Ratings'][$xmlTagSafeClassName];
171
        $this->assertEquals(2, count($ratingsArr));
172
        $ratingIDs = array(
173
            (int)$ratingsArr[0]['@attributes']['id'],
174
            (int)$ratingsArr[1]['@attributes']['id']
175
        );
176
        $this->assertContains($rating1->ID, $ratingIDs);
177
        $this->assertContains($rating2->ID, $ratingIDs);
178
    }
179
180
    public function testGETRelationshipsWithAlias()
181
    {
182
        // Alias do not currently work with Relationships
183
        Config::inst()->set(RestfulServerTestAuthor::class, 'api_field_mapping', ['stars' => 'Ratings']);
184
        $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1');
185
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
186
187
        // @todo should be set up by fixtures, doesn't work for some reason...
188
        $author1->Ratings()->add($rating1);
189
190
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
191
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . '?add_fields=stars';
192
        $response = Director::test($url, null, null, 'GET');
193
        $this->assertEquals(200, $response->getStatusCode());
194
195
        $responseArr = Convert::xml2array($response->getBody());
196
        $xmlTagSafeClassName = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
0 ignored issues
show
Unused Code introduced by
The assignment to $xmlTagSafeClassName is dead and can be removed.
Loading history...
197
198
        $this->assertTrue(array_key_exists('Ratings', $responseArr));
199
        $this->assertFalse(array_key_exists('stars', $responseArr));
200
    }
201
202
    public function testGETManyManyRelationshipsXML()
203
    {
204
        // author4 has related authors author2 and author3
205
        $author2 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author2');
206
        $author3 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author3');
207
        $author4 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author4');
208
209
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
210
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author4->ID . '/RelatedAuthors';
211
        $response = Director::test($url, null, null, 'GET');
212
        $this->assertEquals(200, $response->getStatusCode());
213
        $arr = Convert::xml2array($response->getBody());
214
        $xmlSafeClassName = $this->urlSafeClassname(RestfulServerTestAuthor::class);
215
        $authorsArr = $arr[$xmlSafeClassName];
216
217
        $this->assertEquals(2, count($authorsArr));
218
        $ratingIDs = array(
219
            (int)$authorsArr[0]['ID'],
220
            (int)$authorsArr[1]['ID']
221
        );
222
        $this->assertContains($author2->ID, $ratingIDs);
223
        $this->assertContains($author3->ID, $ratingIDs);
224
    }
225
226
    public function testPUTWithFormEncoded()
227
    {
228
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
229
230
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
231
        $_SERVER['PHP_AUTH_PW'] = 'editor';
232
233
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
234
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
235
        $body = 'Name=Updated Comment&Comment=updated';
236
        $headers = array(
237
            'Content-Type' => 'application/x-www-form-urlencoded'
238
        );
239
        $response = Director::test($url, null, null, 'PUT', $body, $headers);
240
        $this->assertEquals(202, $response->getStatusCode()); // Accepted
241
        // Assumption: XML is default output
242
        $responseArr = Convert::xml2array($response->getBody());
243
        $this->assertEquals($comment1->ID, $responseArr['ID']);
244
        $this->assertEquals('updated', $responseArr['Comment']);
245
        $this->assertEquals('Updated Comment', $responseArr['Name']);
246
247
        unset($_SERVER['PHP_AUTH_USER']);
248
        unset($_SERVER['PHP_AUTH_PW']);
249
    }
250
251
    public function testPOSTWithFormEncoded()
252
    {
253
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
254
255
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
256
        $_SERVER['PHP_AUTH_PW'] = 'editor';
257
258
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
259
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname";
260
        $body = 'Name=New Comment&Comment=created';
261
        $headers = array(
262
            'Content-Type' => 'application/x-www-form-urlencoded'
263
        );
264
        $response = Director::test($url, null, null, 'POST', $body, $headers);
265
        $this->assertEquals(201, $response->getStatusCode()); // Created
266
        // Assumption: XML is default output
267
        $responseArr = Convert::xml2array($response->getBody());
268
        $this->assertTrue($responseArr['ID'] > 0);
269
        $this->assertNotEquals($responseArr['ID'], $comment1->ID);
270
        $this->assertEquals('created', $responseArr['Comment']);
271
        $this->assertEquals('New Comment', $responseArr['Name']);
272
        $this->assertEquals(
273
            Controller::join_links($url, $responseArr['ID'] . '.xml'),
274
            $response->getHeader('Location')
275
        );
276
277
        unset($_SERVER['PHP_AUTH_USER']);
278
        unset($_SERVER['PHP_AUTH_PW']);
279
    }
280
281
    public function testPostWithoutBodyReturnsNoContent()
282
    {
283
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
284
        $_SERVER['PHP_AUTH_PW'] = 'editor';
285
286
        $url = "{$this->baseURI}/api/v1/" . RestfulServerTestComment::class;
287
        $response = Director::test($url, null, null, 'POST');
288
289
        $this->assertEquals('No Content', $response->getBody());
290
291
        unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
292
    }
293
294
    public function testPUTwithJSON()
295
    {
296
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
297
298
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
299
        $_SERVER['PHP_AUTH_PW'] = 'editor';
300
301
        // by acceptance mimetype
302
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
303
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
304
        $body = '{"Comment":"updated"}';
305
        $response = Director::test($url, null, null, 'PUT', $body, array(
306
            'Content-Type'=>'application/json',
307
            'Accept' => 'application/json'
308
        ));
309
        $this->assertEquals(202, $response->getStatusCode()); // Accepted
310
        $obj = json_decode($response->getBody());
311
        $this->assertEquals($comment1->ID, $obj->ID);
312
        $this->assertEquals('updated', $obj->Comment);
313
314
        // by extension
315
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
316
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/{$comment1->ID}.json";
317
        $body = '{"Comment":"updated"}';
318
        $response = Director::test($url, null, null, 'PUT', $body);
319
        $this->assertEquals(202, $response->getStatusCode()); // Accepted
320
        $this->assertEquals($url, $response->getHeader('Location'));
321
        $obj = json_decode($response->getBody());
322
        $this->assertEquals($comment1->ID, $obj->ID);
323
        $this->assertEquals('updated', $obj->Comment);
324
325
        unset($_SERVER['PHP_AUTH_USER']);
326
        unset($_SERVER['PHP_AUTH_PW']);
327
    }
328
329
    public function testPUTwithXML()
330
    {
331
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
332
333
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
334
        $_SERVER['PHP_AUTH_PW'] = 'editor';
335
336
        // by mimetype
337
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
338
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
339
        $body = '<RestfulServerTestComment><Comment>updated</Comment></RestfulServerTestComment>';
340
        $response = Director::test($url, null, null, 'PUT', $body, array('Content-Type'=>'text/xml'));
341
        $this->assertEquals(202, $response->getStatusCode()); // Accepted
342
        $obj = Convert::xml2array($response->getBody());
343
        $this->assertEquals($comment1->ID, $obj['ID']);
344
        $this->assertEquals('updated', $obj['Comment']);
345
346
        // by extension
347
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
348
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/{$comment1->ID}.xml";
349
        $body = '<RestfulServerTestComment><Comment>updated</Comment></RestfulServerTestComment>';
350
        $response = Director::test($url, null, null, 'PUT', $body);
351
        $this->assertEquals(202, $response->getStatusCode()); // Accepted
352
        $this->assertEquals($url, $response->getHeader('Location'));
353
        $obj = Convert::xml2array($response->getBody());
354
        $this->assertEquals($comment1->ID, $obj['ID']);
355
        $this->assertEquals('updated', $obj['Comment']);
356
357
        unset($_SERVER['PHP_AUTH_USER']);
358
        unset($_SERVER['PHP_AUTH_PW']);
359
    }
360
361
    public function testHTTPAcceptAndContentType()
362
    {
363
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
364
365
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
366
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
367
368
        $headers = array('Accept' => 'application/json');
369
        $response = Director::test($url, null, null, 'GET', null, $headers);
370
        $this->assertEquals(200, $response->getStatusCode()); // Success
371
        $obj = json_decode($response->getBody());
372
        $this->assertEquals($comment1->ID, $obj->ID);
373
        $this->assertEquals('application/json', $response->getHeader('Content-Type'));
374
    }
375
376
    public function testNotFound()
377
    {
378
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
379
        $_SERVER['PHP_AUTH_PW'] = 'user';
380
381
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
382
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/99";
383
        $response = Director::test($url, null, null, 'GET');
384
        $this->assertEquals(404, $response->getStatusCode());
385
386
        unset($_SERVER['PHP_AUTH_USER']);
387
        unset($_SERVER['PHP_AUTH_PW']);
388
    }
389
390
    public function testMethodNotAllowed()
391
    {
392
        $comment1 = $this->objFromFixture(RestfulServerTestComment::class, 'comment1');
393
394
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
395
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $comment1->ID;
396
        $response = Director::test($url, null, null, 'UNKNOWNHTTPMETHOD');
397
        $this->assertEquals(405, $response->getStatusCode());
398
    }
399
400
    public function testConflictOnExistingResourceWhenUsingPost()
401
    {
402
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
403
404
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
405
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID;
406
        $response = Director::test($url, null, null, 'POST');
407
        $this->assertEquals(409, $response->getStatusCode());
408
    }
409
410
    public function testUnsupportedMediaType()
411
    {
412
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
413
        $_SERVER['PHP_AUTH_PW'] = 'user';
414
415
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestComment::class);
416
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname";
417
        $data = "Comment||\/||updated"; // weird format
418
        $headers = array('Content-Type' => 'text/weirdformat');
419
        $response = Director::test($url, null, null, 'POST', $data, $headers);
420
        $this->assertEquals(415, $response->getStatusCode());
421
422
        unset($_SERVER['PHP_AUTH_USER']);
423
        unset($_SERVER['PHP_AUTH_PW']);
424
    }
425
426
    public function testXMLValueFormatting()
427
    {
428
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
429
430
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
431
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID;
432
        $response = Director::test($url, null, null, 'GET');
433
        $this->assertContains('<ID>' . $rating1->ID . '</ID>', $response->getBody());
434
        $this->assertContains('<Rating>' . $rating1->Rating . '</Rating>', $response->getBody());
435
    }
436
437
    public function testXMLValueFormattingWithFieldAlias()
438
    {
439
        Config::inst()->set(RestfulServerTestAuthorRating::class, 'api_field_mapping', ['rate' => 'Rating']);
440
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
441
442
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
443
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID;
444
        $response = Director::test($url, null, null, 'GET');
445
        $this->assertContains('<rate>' . $rating1->Rating . '</rate>', $response->getBody());
446
    }
447
448
    public function testApiAccessFieldRestrictions()
449
    {
450
        $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1');
451
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
452
453
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
454
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID;
455
        $response = Director::test($url, null, null, 'GET');
456
        $this->assertContains('<ID>', $response->getBody());
457
        $this->assertContains('<Rating>', $response->getBody());
458
        $this->assertContains('<Author', $response->getBody());
459
        $this->assertNotContains('<SecretField>', $response->getBody());
460
        $this->assertNotContains('<SecretRelation>', $response->getBody());
461
462
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
463
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID . '?add_fields=SecretField,SecretRelation';
464
        $response = Director::test($url, null, null, 'GET');
465
        $this->assertNotContains(
466
            '<SecretField>',
467
            $response->getBody(),
468
            '"add_fields" URL parameter filters out disallowed fields from $api_access'
469
        );
470
        $this->assertNotContains(
471
            '<SecretRelation>',
472
            $response->getBody(),
473
            '"add_fields" URL parameter filters out disallowed relations from $api_access'
474
        );
475
476
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
477
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID . '?fields=SecretField,SecretRelation';
478
        $response = Director::test($url, null, null, 'GET');
479
        $this->assertNotContains(
480
            '<SecretField>',
481
            $response->getBody(),
482
            '"fields" URL parameter filters out disallowed fields from $api_access'
483
        );
484
        $this->assertNotContains(
485
            '<SecretRelation>',
486
            $response->getBody(),
487
            '"fields" URL parameter filters out disallowed relations from $api_access'
488
        );
489
490
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
491
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . '/Ratings';
492
        $response = Director::test($url, null, null, 'GET');
493
        $this->assertContains(
494
            '<Rating>',
495
            $response->getBody(),
496
            'Relation viewer shows fields allowed through $api_access'
497
        );
498
        $this->assertNotContains(
499
            '<SecretField>',
500
            $response->getBody(),
501
            'Relation viewer on has-many filters out disallowed fields from $api_access'
502
        );
503
    }
504
505
    public function testApiAccessRelationRestrictionsInline()
506
    {
507
        $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1');
508
509
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
510
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID;
511
        $response = Director::test($url, null, null, 'GET');
512
        $this->assertNotContains('<RelatedPages', $response->getBody(), 'Restricts many-many with api_access=false');
513
        $this->assertNotContains('<PublishedPages', $response->getBody(), 'Restricts has-many with api_access=false');
514
    }
515
516
    public function testApiAccessRelationRestrictionsOnEndpoint()
517
    {
518
        $author1 = $this->objFromFixture(RestfulServerTestAuthor::class, 'author1');
519
520
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
521
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . "/ProfilePage";
522
        $response = Director::test($url, null, null, 'GET');
523
        $this->assertEquals(404, $response->getStatusCode(), 'Restricts has-one with api_access=false');
524
525
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
526
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . "/RelatedPages";
527
        $response = Director::test($url, null, null, 'GET');
528
        $this->assertEquals(404, $response->getStatusCode(), 'Restricts many-many with api_access=false');
529
530
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
531
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $author1->ID . "/PublishedPages";
532
        $response = Director::test($url, null, null, 'GET');
533
        $this->assertEquals(404, $response->getStatusCode(), 'Restricts has-many with api_access=false');
534
    }
535
536
    public function testApiAccessWithPUT()
537
    {
538
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
539
540
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
541
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID;
542
        $data = array(
543
            'Rating' => '42',
544
            'WriteProtectedField' => 'haxx0red'
545
        );
546
        $response = Director::test($url, $data, null, 'PUT');
547
        // Assumption: XML is default output
548
        $responseArr = Convert::xml2array($response->getBody());
549
        $this->assertEquals(42, $responseArr['Rating']);
550
        $this->assertNotEquals('haxx0red', $responseArr['WriteProtectedField']);
551
    }
552
553
    public function testFieldAliasWithPUT()
554
    {
555
        Config::inst()->set(RestfulServerTestAuthorRating::class, 'api_field_mapping', ['rate' => 'Rating']);
556
        $rating1 = $this->objFromFixture(RestfulServerTestAuthorRating::class, 'rating1');
557
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
558
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/" . $rating1->ID;
559
        // Test input with original fieldname
560
        $data = array(
561
            'Rating' => '42',
562
        );
563
        $response = Director::test($url, $data, null, 'PUT');
564
        // Assumption: XML is default output
565
        $responseArr = Convert::xml2array($response->getBody());
566
        // should output with aliased name
567
        $this->assertEquals(42, $responseArr['rate']);
568
    }
569
570
    public function testJSONDataFormatter()
571
    {
572
        $formatter = new JSONDataFormatter();
573
        $editor = $this->objFromFixture(Member::class, 'editor');
574
        $user = $this->objFromFixture(Member::class, 'user');
575
576
        // The DataFormatter performs canView calls
577
        // these are `Member`s so we need to be ADMIN types
578
        $this->logInWithPermission('ADMIN');
579
580
        $this->assertEquals(
581
            '{"FirstName":"Editor","Email":"[email protected]"}',
582
            $formatter->convertDataObject($editor, ["FirstName", "Email"]),
583
            "Correct JSON formatting with field subset"
584
        );
585
586
        $set = Member::get()
587
            ->filter('ID', [$editor->ID, $user->ID])
588
            ->sort('"Email" ASC'); // for sorting for postgres
589
        $this->assertEquals(
590
            '{"totalSize":null,"items":[{"FirstName":"Editor","Email":"[email protected]"},' .
591
                '{"FirstName":"User","Email":"[email protected]"}]}',
592
            $formatter->convertDataObjectSet($set, ["FirstName", "Email"]),
593
            "Correct JSON formatting on a dataobjectset with field filter"
594
        );
595
    }
596
597
    public function testJSONDataFormatterWithFieldAlias()
598
    {
599
        Config::inst()->set(Member::class, 'api_field_mapping', ['MyName' => 'FirstName']);
600
        $formatter = new JSONDataFormatter();
601
        $editor = $this->objFromFixture(Member::class, 'editor');
602
        $user = $this->objFromFixture(Member::class, 'user');
603
604
        // The DataFormatter performs canView calls
605
        // these are `Member`s so we need to be ADMIN types
606
        $this->logInWithPermission('ADMIN');
607
608
        $set = Member::get()
609
            ->filter('ID', [$editor->ID, $user->ID])
610
            ->sort('"Email" ASC'); // for sorting for postgres
611
612
        $this->assertEquals(
613
            '{"totalSize":null,"items":[{"MyName":"Editor","Email":"[email protected]"},' .
614
                '{"MyName":"User","Email":"[email protected]"}]}',
615
            $formatter->convertDataObjectSet($set, ["FirstName", "Email"]),
616
            "Correct JSON formatting with field alias"
617
        );
618
    }
619
620
    public function testGetWithSortDescending()
621
    {
622
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
623
        $url = "{$this->baseURI}/api/v1/{$urlSafeClassname}?sort=FirstName&dir=DESC&fields=FirstName";
624
625
        $response = Director::test($url);
626
        $results = Convert::xml2array($response->getBody());
627
628
        $this->assertSame('Author 4', $results[$urlSafeClassname][0]['FirstName']);
629
        $this->assertSame('Author 3', $results[$urlSafeClassname][1]['FirstName']);
630
        $this->assertSame('Author 2', $results[$urlSafeClassname][2]['FirstName']);
631
        $this->assertSame('Author 1', $results[$urlSafeClassname][3]['FirstName']);
632
    }
633
634
    public function testGetWithSortAscending()
635
    {
636
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
637
        $url = "{$this->baseURI}/api/v1/{$urlSafeClassname}?sort=FirstName&dir=ASC&fields=FirstName";
638
639
        $response = Director::test($url);
640
        $results = Convert::xml2array($response->getBody());
641
642
        $this->assertSame('Author 1', $results[$urlSafeClassname][0]['FirstName']);
643
        $this->assertSame('Author 2', $results[$urlSafeClassname][1]['FirstName']);
644
        $this->assertSame('Author 3', $results[$urlSafeClassname][2]['FirstName']);
645
        $this->assertSame('Author 4', $results[$urlSafeClassname][3]['FirstName']);
646
    }
647
648
    public function testGetSortsByIdWhenInvalidSortColumnIsProvided()
649
    {
650
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthor::class);
651
        $url = "{$this->baseURI}/api/v1/{$urlSafeClassname}?sort=Surname&dir=DESC&fields=FirstName";
652
653
        $response = Director::test($url);
654
655
        $results = Convert::xml2array($response->getBody());
656
657
        $this->assertSame('Author 1', $results[$urlSafeClassname][0]['FirstName']);
658
        $this->assertSame('Author 2', $results[$urlSafeClassname][1]['FirstName']);
659
        $this->assertSame('Author 3', $results[$urlSafeClassname][2]['FirstName']);
660
        $this->assertSame('Author 4', $results[$urlSafeClassname][3]['FirstName']);
661
    }
662
663
    public function testApiAccessWithPOST()
664
    {
665
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
666
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/";
667
        $data = [
668
            'Rating' => '42',
669
            'WriteProtectedField' => 'haxx0red'
670
        ];
671
        $response = Director::test($url, $data, null, 'POST');
672
        // Assumption: XML is default output
673
        $responseArr = Convert::xml2array($response->getBody());
674
        $this->assertEquals(42, $responseArr['Rating']);
675
        $this->assertNotEquals('haxx0red', $responseArr['WriteProtectedField']);
676
    }
677
678
    public function testFieldAliasWithPOST()
679
    {
680
        Config::inst()->set(RestfulServerTestAuthorRating::class, 'api_field_mapping', ['rate' => 'Rating']);
681
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestAuthorRating::class);
682
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/";
683
        $data = [
684
            'rate' => '42',
685
        ];
686
        $response = Director::test($url, $data, null, 'POST');
687
        $responseArr = Convert::xml2array($response->getBody());
688
        $this->assertEquals(42, $responseArr['rate']);
689
    }
690
691
    public function testCanViewRespectedInList()
692
    {
693
        // Default content type
694
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestSecretThing::class);
695
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/";
696
        $response = Director::test($url, null, null, 'GET');
697
        $this->assertEquals(200, $response->getStatusCode());
698
        $this->assertNotContains('Unspeakable', $response->getBody());
699
700
        // JSON content type
701
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname.json";
702
        $response = Director::test($url, null, null, 'GET');
703
        $this->assertEquals(200, $response->getStatusCode());
704
        $this->assertNotContains('Unspeakable', $response->getBody());
705
        $responseArray = json_decode($response->getBody(), true);
706
        $this->assertSame(0, $responseArray['totalSize']);
707
708
        // With authentication
709
        $_SERVER['PHP_AUTH_USER'] = '[email protected]';
710
        $_SERVER['PHP_AUTH_PW'] = 'editor';
711
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestSecretThing::class);
712
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/";
713
        $response = Director::test($url, null, null, 'GET');
714
        $this->assertEquals(200, $response->getStatusCode());
715
        $this->assertContains('Unspeakable', $response->getBody());
716
        // Assumption: default formatter is XML
717
        $responseArray = Convert::xml2array($response->getBody());
718
        $this->assertEquals(1, $responseArray['@attributes']['totalSize']);
719
        unset($_SERVER['PHP_AUTH_USER']);
720
        unset($_SERVER['PHP_AUTH_PW']);
721
    }
722
723
    public function testValidationErrorWithPOST()
724
    {
725
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestValidationFailure::class);
726
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/";
727
        $data = [
728
            'Content' => 'Test',
729
        ];
730
        $response = Director::test($url, $data, null, 'POST');
731
        // Assumption: XML is default output
732
        $responseArr = Convert::xml2array($response->getBody());
733
        $this->assertEquals('SilverStripe\\ORM\\ValidationException', $responseArr['type']);
734
    }
735
736
    public function testExceptionThrownWithPOST()
737
    {
738
        $urlSafeClassname = $this->urlSafeClassname(RestfulServerTestExceptionThrown::class);
739
        $url = "{$this->baseURI}/api/v1/$urlSafeClassname/";
740
        $data = [
741
            'Content' => 'Test',
742
        ];
743
        $response = Director::test($url, $data, null, 'POST');
744
        // Assumption: XML is default output
745
        $responseArr = Convert::xml2array($response->getBody());
746
        $this->assertEquals(\Exception::class, $responseArr['type']);
747
    }
748
749
    public function testParseClassName()
750
    {
751
        $manyMany = RestfulServerTestAuthor::config()->get('many_many');
752
753
        // simple syntax (many many standard)
754
        $className = RestfulServer::parseRelationClass($manyMany['RelatedPages']);
755
        $this->assertEquals(RestfulServerTestPage::class, $className);
756
757
        // array syntax (many many through)
758
        $className = RestfulServer::parseRelationClass($manyMany['SortedPages']);
759
        $this->assertEquals(RestfulServerTestPage::class, $className);
760
    }
761
}
762