4
4
* Copyright (C) 2015 Spring Signage Ltd
7
namespace Xibo\Tests\Integration;
8
use Xibo\Helper\Random;
9
use Xibo\OAuth2\Client\Entity\XiboCampaign;
10
use Xibo\OAuth2\Client\Entity\XiboLayout;
11
use Xibo\OAuth2\Client\Entity\XiboRegion;
12
use Xibo\Entity\Layout;
13
use Xibo\Factory\LayoutFactory;
12
14
use Xibo\Tests\LocalWebTestCase;
16
* @package Xibo\Tests\Integration
18
16
class LayoutTest extends LocalWebTestCase
20
protected $startLayouts;
22
* setUp - called before every test automatically
24
public function setup()
27
$this->startLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
30
* tearDown - called after every test automatically
32
public function tearDown()
34
// tearDown all layouts that weren't there initially
35
$finalLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
36
# Loop over any remaining layouts and nuke them
37
foreach ($finalLayouts as $layout) {
38
/** @var XiboLayout $layout */
40
foreach ($this->startLayouts as $startLayout) {
41
if ($startLayout->layoutId == $layout->layoutId) {
48
} catch (\Exception $e) {
49
fwrite(STDERR, 'Unable to delete ' . $layout->layoutId . '. E:' . $e->getMessage());
56
* List all layouts known empty
58
public function testListEmpty()
60
# Check that there is one layout in the database (the 'default layout')
61
if (count($this->startLayouts) > 1) {
62
$this->skipTest("There are pre-existing Layouts");
65
$this->client->get('/layout');
66
$this->assertSame(200, $this->client->response->status());
67
$this->assertNotEmpty($this->client->response->body());
68
$object = json_decode($this->client->response->body());
69
$this->assertObjectHasAttribute('data', $object, $this->client->response->body());
70
# There should be one default layout in the system
71
$this->assertEquals(1, $object->data->recordsTotal);
75
* testAddSuccess - test adding various Layouts that should be valid
76
* @dataProvider provideSuccessCases
78
public function testAddSuccess($layoutName, $layoutDescription, $layoutTemplateId, $layoutResolutionId)
80
# Create layouts with arguments from provideSuccessCases
81
$response = $this->client->post('/layout', [
82
'name' => $layoutName,
83
'description' => $layoutDescription,
84
'layoutId' => $layoutTemplateId,
85
'resolutionId' => $layoutResolutionId
87
$this->assertSame(200, $this->client->response->status(), "Not successful: " . $response);
88
$object = json_decode($this->client->response->body());
89
$this->assertObjectHasAttribute('data', $object);
90
$this->assertObjectHasAttribute('id', $object);
91
$this->assertSame($layoutName, $object->data->layout);
92
$this->assertSame($layoutDescription, $object->data->description);
93
# Check that the layout was really added
94
$layouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
95
$this->assertEquals(count($this->startLayouts) + 1, count($layouts));
96
# Check that the layout was added correctly
97
$layout = (new XiboLayout($this->getEntityProvider()))->getById($object->id);
98
$this->assertSame($layoutName, $layout->layout);
99
$this->assertSame($layoutDescription, $layout->description);
100
# Clean up the Layout as we no longer need it
101
$this->assertTrue($layout->delete(), 'Unable to delete ' . $layout->layoutId);
105
* testAddFailure - test adding various Layouts that should be invalid
106
* @dataProvider provideFailureCases
108
public function testAddFailure($layoutName, $layoutDescription, $layoutTemplateId, $layoutResolutionId)
110
# Create layouts with arguments from provideFailureCases
111
$response = $this->client->post('/layout', [
112
'name' => $layoutName,
113
'description' => $layoutDescription,
114
'layoutId' => $layoutTemplateId,
115
'resolutionId' => $layoutResolutionId
117
# check if they fail as expected
118
$this->assertSame(500, $this->client->response->status(), 'Expecting failure, received ' . $this->client->response->status());
121
* List all layouts known set
124
public function testListKnown()
126
$cases = $this->provideSuccessCases();
128
// Check each possible case to ensure it's not pre-existing
129
// If it is, skip over it
130
foreach ($cases as $case) {
132
foreach ($this->startLayouts as $tmpLayout) {
133
if ($case[0] == $tmpLayout->layout) {
138
$layouts[] = (new XiboLayout($this->getEntityProvider()))->create($case[0],$case[1],$case[2],$case[3]);
141
$this->client->get('/layout');
142
$this->assertSame(200, $this->client->response->status());
143
$this->assertNotEmpty($this->client->response->body());
144
$object = json_decode($this->client->response->body());
145
$this->assertObjectHasAttribute('data', $object, $this->client->response->body());
146
# There should be as many layouts as we created plus the number we started with in the system
147
$this->assertEquals(count($layouts) + count($this->startLayouts), $object->data->recordsTotal);
148
# Clean up the groups we created
149
foreach ($layouts as $lay) {
154
* List specific layouts
157
* @depends testListKnown
158
* @depends testAddSuccess
159
* @dataProvider provideSuccessCases
161
public function testListFilter($layoutName, $layoutDescription, $layoutTemplateId, $layoutResolutionId)
163
if (count($this->startLayouts) > 1) {
164
$this->skipTest("There are pre-existing Layouts");
167
# Load in a known set of layouts
168
# We can assume this works since we depend upon the test which
169
# has previously added and removed these without issue:
170
$cases = $this->provideSuccessCases();
172
foreach ($cases as $case) {
173
$layouts[] = (new XiboLayout($this->getEntityProvider()))->create($case[0], $case[1], $case[2], $case[3]);
175
$this->client->get('/layout', [
176
'name' => $layoutName
178
$this->assertSame(200, $this->client->response->status());
179
$this->assertNotEmpty($this->client->response->body());
180
$object = json_decode($this->client->response->body());
181
$this->assertObjectHasAttribute('data', $object, $this->client->response->body());
182
# There should be at least one match
183
$this->assertGreaterThanOrEqual(1, $object->data->recordsTotal);
185
# Check that for the records returned, $layoutName is in the groups names
186
foreach ($object->data->data as $lay) {
187
if (strpos($layoutName, $lay->layout) == 0) {
191
// The object we got wasn't the exact one we searched for
192
// Make sure all the words we searched for are in the result
193
foreach (array_map('trim',explode(",",$layoutName)) as $word) {
194
assertTrue((strpos($word, $lay->layout) !== false), 'Layout returned did not match the query string: ' . $lay->layout);
198
$this->assertTrue($flag, 'Search term not found');
199
foreach ($layouts as $lay) {
204
* Each array is a test run
205
* Format (LayoutName, description, layoutID (template), resolution ID)
208
public function provideSuccessCases()
210
# Data for testAddSuccess, easily expandable - just add another set of data below
212
// Multi-language layouts
213
'English 1' => ['phpunit test Layout', 'Api', '', 9],
214
'French 1' => ['Test de Français 1', 'Bienvenue à la suite de tests Xibo', '', 9],
215
'German 1' => ['Deutsch Prüfung 1', 'Weiß mit schwarzem Text', '', 9],
216
'Simplified Chinese 1' => ['试验组', '测试组描述', '', 9],
217
'Portrait layout' => ['Portrait layout', '1080x1920', '', 11],
218
'No Description' => ['Just the title and resolution', NULL, '', 11],
219
'Just title' => ['Just the name', NULL, NULL, NULL]
223
* Each array is a test run
224
* Format (LayoutName, description, layoutID (template), resolution ID)
227
public function provideFailureCases()
229
# Data for testAddfailure, easily expandable - just add another set of data below
231
// Description is limited to 255 characters
232
'Description over 254 characters' => ['Too long description', Random::generateString(255), '', 9],
233
// Missing layout names
234
'layout name empty' => ['', 'Layout name is empty', '', 9],
235
'Layout name null' => [null, 'Layout name is null', '', 9],
236
'Wrong resolution ID' => ['id not found', 'not found exception', '', 69]
241
* Try and add two layouts with the same name
243
public function testAddDuplicate()
245
# Check if there are layouts with that name already in the system
247
foreach ($this->startLayouts as $layout) {
248
if ($layout->layout == 'phpunit layout') {
252
# Load in a known layout if it's not there already
254
(new XiboLayout($this->getEntityProvider()))->create('phpunit layout', 'phpunit layout', '', 9);
255
$this->client->post('/layout', [
256
'name' => 'phpunit layout',
257
'description' => 'phpunit layout'
259
$this->assertSame(500, $this->client->response->status(), 'Expecting failure, received ' . $this->client->response->status() . '. Body = ' . $this->client->response->body());
263
* Edit an existing layout
265
public function testEdit()
267
# Check if there are layouts with that name already in the system
268
foreach ($this->startLayouts as $lay) {
269
if ($lay->layout == 'phpunit layout') {
270
$this->skipTest('layout already exists with that name');
274
# Load in a known layout
275
/** @var XiboLayout $layout */
276
$layout = (new XiboLayout($this->getEntityProvider()))->create('phpunit layout', 'phpunit layout', '', 9);
277
# Change the layout name and description
278
$name = Random::generateString(8, 'phpunit');
279
$description = Random::generateString(8, 'description');
280
$this->client->put('/layout/' . $layout->layoutId, [
282
'description' => $description,
283
'backgroundColor' => $layout->backgroundColor,
284
'backgroundzIndex' => $layout->backgroundzIndex
285
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
287
$this->assertSame(200, $this->client->response->status(), 'Not successful: ' . $this->client->response->body());
288
$object = json_decode($this->client->response->body());
290
# Examine the returned object and check that it's what we expect
291
$this->assertObjectHasAttribute('data', $object);
292
$this->assertObjectHasAttribute('id', $object);
293
$this->assertSame($name, $object->data->layout);
294
$this->assertSame($description, $object->data->description);
295
# Check that the layout was actually renamed
296
$layout = (new XiboLayout($this->getEntityProvider()))->getById($object->id);
297
$this->assertSame($name, $layout->layout);
298
$this->assertSame($description, $layout->description);
299
# Clean up the Layout as we no longer need it
304
* Edit an existing layout that should fail because of negative value in the backgroundzIndex
306
public function testEditFailure()
308
# Check if there are layouts with that name already in the system
309
foreach ($this->startLayouts as $lay) {
310
if ($lay->layout == 'phpunit layout') {
311
$this->skipTest('layout already exists with that name');
315
# Load in a known layout
316
/** @var XiboLayout $layout */
317
$layout = (new XiboLayout($this->getEntityProvider()))->create('phpunit layout', 'phpunit layout', '', 9);
318
# Change the layout name and description
319
$name = Random::generateString(8, 'phpunit');
320
$description = Random::generateString(8, 'description');
321
$this->client->put('/layout/' . $layout->layoutId, [
323
'description' => $description,
324
'backgroundColor' => $layout->backgroundColor,
325
'backgroundzIndex' => -1
326
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
327
$this->assertSame(500, $this->client->response->status(), 'Expecting failure, received ' . $this->client->response->status());
334
public function testDelete()
336
$name1 = Random::generateString(8, 'phpunit');
337
$name2 = Random::generateString(8, 'phpunit');
338
# Load in a couple of known layouts
339
$layout1 = (new XiboLayout($this->getEntityProvider()))->create($name1, 'phpunit description', '', 9);
340
$layout2 = (new XiboLayout($this->getEntityProvider()))->create($name2, 'phpunit description', '', 9);
341
# Delete the one we created last
342
$this->client->delete('/layout/' . $layout2->layoutId);
343
# This should return 204 for success
344
$response = json_decode($this->client->response->body());
345
$this->assertSame(204, $response->status, $this->client->response->body());
346
# Check only one remains
347
$layouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
348
$this->assertEquals(count($this->startLayouts) + 1, count($layouts));
350
foreach ($layouts as $layout) {
351
if ($layout->layoutId == $layout1->layoutId) {
355
$this->assertTrue($flag, 'Layout ID ' . $layout1->layoutId . ' was not found after deleting a different layout');
360
* Try to delete a layout that is assigned to a campaign
362
public function testDeleteAssigned()
364
# Load in a known layout
365
/** @var XiboLayout $layout */
366
$layout = (new XiboLayout($this->getEntityProvider()))->create('phpunit layout assigned', 'phpunit layout', '', 9);
367
// Make a campaign with a known name
368
$name = Random::generateString(8, 'phpunit');
369
/* @var XiboCampaign $campaign */
370
$campaign = (new XiboCampaign($this->getEntityProvider()))->create($name);
371
$this->assertGreaterThan(0, count($layout), 'Cannot find layout for test');
372
// Assign layout to campaign
373
$campaign->assignLayout($layout->layoutId);
374
# Check if it's assigned
375
$campaignCheck = (new XiboCampaign($this->getEntityProvider()))->getById($campaign->campaignId);
376
$this->assertSame(1, $campaignCheck->numberLayouts);
377
# Try to Delete the layout assigned to the campaign
378
$this->client->delete('/layout/' . $layout->layoutId);
379
# This should return 204 for success
380
$response = json_decode($this->client->response->body());
381
$this->assertSame(204, $response->status, $this->client->response->body());
388
18
public function testRetire()
391
$layout = (new XiboLayout($this->getEntityProvider()))->create('test layout', 'test description', '', 9);
21
$layout = LayoutFactory::query(null, ['start' => 1, 'length' => 1])[0];
393
24
$this->client->put('/layout/retire/' . $layout->layoutId, [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
394
26
$this->assertSame(200, $this->client->response->status());
395
28
// Get the same layout again and make sure its retired = 1
396
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layout->layoutId);
29
$layout = LayoutFactory::getById($layout->layoutId);
397
31
$this->assertSame(1, $layout->retired, 'Retired flag not updated');
398
return $layout->layoutId;
402
* @param Layout $layoutId
37
* @param Layout $layout
403
38
* @depends testRetire
405
public function testUnretire($layoutId)
40
public function testUnretire($layout)
407
// Get back the layout details for this ID
408
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layoutId);
409
// Reset the flag to retired
410
42
$layout->retired = 0;
411
// Call layout edit with this Layout
412
43
$this->client->put('/layout/' . $layout->layoutId, array_merge((array)$layout, ['name' => $layout->layout]), ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
413
45
$this->assertSame(200, $this->client->response->status(), $this->client->response->body());
414
// Get the same layout again and make sure its retired = 0
415
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layout->layoutId);
47
// Get the same layout again and make sure its retired = 1
48
$layout = LayoutFactory::getById($layout->layoutId);
416
50
$this->assertSame(0, $layout->retired, 'Retired flag not updated. ' . $this->client->response->body());
420
* Add new region to a specific layout
421
* @dataProvider regionSuccessCases
423
public function testAddRegionSuccess($regionWidth, $regionHeight, $regionTop, $regionLeft)
425
# Create random and and layout
426
$name = Random::generateString(8, 'phpunit');
427
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', 9);
428
# Add region to our layout with data from regionSuccessCases
429
$this->client->post('/region/' . $layout->layoutId, [
430
'width' => $regionWidth,
431
'height' => $regionHeight,
433
'left' => $regionLeft
435
$this->assertSame(200, $this->client->response->status());
436
$object = json_decode($this->client->response->body());
437
$this->assertObjectHasAttribute('data', $object);
438
$this->assertObjectHasAttribute('id', $object);
439
# Check if region has intended values
440
$this->assertSame($regionWidth, $object->data->width);
441
$this->assertSame($regionHeight, $object->data->height);
442
$this->assertSame($regionTop, $object->data->top);
443
$this->assertSame($regionLeft, $object->data->left);
445
$this->assertTrue($layout->delete(), 'Unable to delete ' . $layout->layoutId);
449
* Each array is a test run
450
* Format (width, height, top, left)
453
public function regionSuccessCases()
456
// various correct regions
457
'region 1' => [500, 350, 100, 150],
458
'region 2' => [350, 200, 50, 50],
459
'region 3' => [69, 69, 20, 420],
460
'region 4 no offsets' => [69, 69, 0, 0]
465
* testAddFailure - test adding various regions that should be invalid
466
* @dataProvider regionFailureCases
468
public function testAddRegionFailure($regionWidth, $regionHeight, $regionTop, $regionLeft, $expectedHttpCode, $expectedWidth, $expectedHeight)
470
# Create random name and layout
471
$name = Random::generateString(8, 'phpunit');
472
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', 9);
473
# Add region to our layout with datafrom regionFailureCases
474
$response = $this->client->post('/region/' . $layout->layoutId, [
475
'width' => $regionWidth,
476
'height' => $regionHeight,
478
'left' => $regionLeft
480
# Check if we receive failure as expected
481
$this->assertSame($expectedHttpCode, $this->client->response->status(), 'Expecting failure, received ' . $this->client->response->status());
482
if ($expectedHttpCode == 200) {
483
$object = json_decode($this->client->response->body());
484
$this->assertObjectHasAttribute('data', $object);
485
$this->assertObjectHasAttribute('id', $object);
486
$this->assertSame($expectedWidth, $object->data->width);
487
$this->assertSame($expectedHeight, $object->data->height);
492
* Each array is a test run
493
* Format (width, height, top, left)
496
public function regionFailureCases()
499
// various incorrect regions
500
'region no size' => [NULL, NULL, 20, 420, 200, 250, 250],
501
'region negative dimensions' => [-69, -420, 20, 420, 500, null, null]
508
public function testEditRegion()
510
# Create layout with random name
511
$name = Random::generateString(8, 'phpunit');
512
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', 9);
513
# Add region to our layout
514
$region = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,75,125);
516
$this->client->put('/region/' . $region->regionId, [
523
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
524
# Check if successful
525
$this->assertSame(200, $this->client->response->status());
526
$object = json_decode($this->client->response->body());
527
$this->assertObjectHasAttribute('data', $object);
528
$this->assertObjectHasAttribute('id', $object);
529
# Check if region has updated values
530
$this->assertSame(700, $object->data->width);
531
$this->assertSame(500, $object->data->height);
532
$this->assertSame(400, $object->data->top);
533
$this->assertSame(400, $object->data->left);
537
* Edit known region that should fail because of negative z-index value
539
public function testEditRegionFailure()
541
# Create layout with random name
542
$name = Random::generateString(8, 'phpunit');
543
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', 9);
544
# Add region to our layout
545
$region = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,75,125);
547
$this->client->put('/region/' . $region->regionId, [
554
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
556
$this->assertSame(500, $this->client->response->status(), 'Expecting failure, received ' . $this->client->response->status());
562
public function testDeleteRegion()
564
# Create layout and add region to it
565
$name = Random::generateString(8, 'phpunit');
566
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', 9);
567
$region = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200, 670, 100, 100);
569
$this->client->delete('/region/' . $region->regionId);
570
$this->assertSame(200, $this->client->response->status(), $this->client->response->body());
576
* Add tag to a layout
578
public function testAddTag()
581
$name = Random::generateString(8, 'phpunit');
582
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', 9);
583
# Assign new tag to our layout
584
$this->client->post('/layout/' . $layout->layoutId . '/tag' , [
587
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layout->layoutId);
588
$this->assertSame(200, $this->client->response->status(), $this->client->response->body());
589
$this->assertSame('API', $layout->tags);
593
* Delete tags from layout
596
public function testDeleteTag()
598
$name = Random::generateString(8, 'phpunit');
599
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', 9);
601
$layout->addTag($tag);
602
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layout->layoutId);
603
print_r($layout->tags);
604
$this->client->delete('/layout/' . $layout->layoutId . '/untag', [
608
$this->assertSame(200, $this->client->response->status(), 'Not successful: ' . $this->client->response->body());
613
* Calculate layout status
615
public function testStatus()
618
$name = Random::generateString(8, 'phpunit');
619
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', 9);
620
# Calculate layouts status
621
$this->client->get('/layout/status/' . $layout->layoutId);
622
$this->assertSame(200, $this->client->response->status(), $this->client->response->body());
628
public function testCopy()
630
# Load in a known layout
631
/** @var XiboLayout $layout */
632
$layout = (new XiboLayout($this->getEntityProvider()))->create('phpunit layout', 'phpunit layout', '', 9);
633
// Generate new random name
634
$nameCopy = Random::generateString(8, 'phpunit');
636
$this->client->post('/layout/copy/' . $layout->layoutId, [
638
'description' => 'Copy',
639
'copyMediaFiles' => 1
640
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
641
$this->assertSame(200, $this->client->response->status());
642
$object = json_decode($this->client->response->body());
643
$this->assertObjectHasAttribute('data', $object);
644
$this->assertObjectHasAttribute('id', $object);
645
# Check if copied layout has correct name
646
$this->assertSame($nameCopy, $object->data->layout);
647
# Clean up the Layout as we no longer need it
648
$this->assertTrue($layout->delete(), 'Unable to delete ' . $layout->layoutId);
654
public function testPosition()
656
# Load in a known layout
657
/** @var XiboLayout $layout */
658
$layout = (new XiboLayout($this->getEntityProvider()))->create('phpunit layout position', 'phpunit layout', '', 9);
659
# Create Two known regions and add them to that layout
660
$region1 = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,670,75,125);
661
$region2 = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,475,625);
663
# Reposition regions on that layout
664
$regionJson = json_encode([
666
'regionid' => $region1->regionId,
673
'regionid' => $region2->regionId,
680
$this->client->put('/region/position/all/' . $layout->layoutId, [
681
'regions' => $regionJson
682
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
683
# Check if successful
684
$this->assertSame(200, $this->client->response->status());
685
$object = json_decode($this->client->response->body());
691
* Position Test with incorrect parameters (missing height and incorrect spelling)
693
public function testPositionFailure()
695
# Load in a known layout
696
/** @var XiboLayout $layout */
697
$layout = (new XiboLayout($this->getEntityProvider()))->create('phpunit layout position', 'phpunit layout', '', 9);
698
# Create Two known regions and add them to that layout
699
$region1 = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,670,75,125);
700
$region2 = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,475,625);
701
# Reposition regions on that layout with incorrect/missing parameters
702
$regionJson = json_encode([
704
'regionid' => $region1->regionId,
710
'regionid' => $region2->regionId,
716
$this->client->put('/region/position/all/' . $layout->layoutId, [
717
'regions' => $regionJson
718
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
719
# Check if it fails as expected
720
$this->assertSame(500, $this->client->response->status(), 'Expecting failure, received ' . $this->client->response->status());
721
$object = json_decode($this->client->response->body());
b'\\ No newline at end of file'