init commit

This commit is contained in:
Matt Batchelder
2026-02-11 20:55:38 -05:00
commit 6e3929c459
2240 changed files with 467828 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use League\OAuth2\Client\Token\AccessToken;
/**
* Class AboutTest
* @package Xibo\Tests\Integration
*/
class AboutTest extends \Xibo\Tests\LocalWebTestCase
{
/**
* Shows CMS version
* @throws \Exception
*/
public function testVersion()
{
$response = $this->sendRequest('GET', '/about');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$body = json_decode($response->getBody());
$this->assertSame(200, $body->status);
$this->assertSame(true, $body->success);
$this->assertSame(false, $body->grid);
$this->assertNotEmpty($body->data, 'Empty Data');
$this->assertNotEmpty($body->data->version, 'Empty Version');
}
/**
* Test that the API is initialised and making authenticated requests.
*/
public function testApiInitialisedTest()
{
$this->assertNotNull($this->getEntityProvider(), 'Entity Provider not set');
$this->assertNotNull($this->getEntityProvider()->getProvider(), 'Provider not set');
}
/**
* @depends testApiInitialisedTest
*/
public function testApiAccessTest()
{
$provider = $this->getEntityProvider()->getProvider();
$token = $provider->getAccessToken('client_credentials');
$this->assertNotNull($token);
$this->assertNotTrue($token->hasExpired(), 'Expired Token');
$this->assertInstanceOf('League\OAuth2\Client\Token\AccessToken', $token);
return $token;
}
/**
* @param AccessToken $token
* @depends testApiAccessTest
*/
public function testApiUserTest(AccessToken $token)
{
$provider = $this->getEntityProvider()->getProvider();
try {
$me = $provider->getResourceOwner($token);
} catch (\Exception $exception) {
$this->fail('API connect not successful: ' . $exception->getMessage());
}
$this->assertNotNull($me);
$this->assertArrayHasKey('userId', $me->toArray());
$this->assertNotEmpty($me->getId());
$this->assertNotEquals(0, $me->getId());
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
class AuditLogTest extends \Xibo\Tests\LocalWebTestCase
{
public function testSearch()
{
$response = $this->sendRequest('GET','/audit');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertObjectHasAttribute('data', $object->data, $response->getBody());
$this->assertObjectHasAttribute('draw', $object->data, $response->getBody());
$this->assertObjectHasAttribute('recordsTotal', $object->data, $response->getBody());
$this->assertObjectHasAttribute('recordsFiltered', $object->data, $response->getBody());
// Make sure the recordsTotal is not greater than 10 (the default paging)
$this->assertLessThanOrEqual(10, count($object->data->data));
}
public function testExportForm()
{
$response = $this->sendRequest('GET','/audit/form/export');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
}
/**
*/
public function testExport()
{
$response = $this->sendRequest('GET','/audit/export', [
'filterFromDt' => Carbon::now()->subSeconds(86400)->format(DateFormatHelper::getSystemFormat()),
'filterToDt' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
]);
$this->assertSame(200, $response->getStatusCode());
}
}

View File

@@ -0,0 +1,147 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCampaign;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class CampaignDeleteTest
* @package Xibo\Tests\integration\Cache
*/
class CampaignDeleteTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboCampaign */
protected $campaign;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboSchedule */
protected $event;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a simple widget
$this->addSimpleWidget($layout);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Campaign
$this->campaign = (new XiboCampaign($this->getEntityProvider()))->create(Random::generateString());
// Assign the Layout to the Campaign
$this->getEntityProvider()->post('/campaign/layout/assign/' . $this->campaign->campaignId, [
'layoutId' => $this->layout->layoutId
]);
// Create a Display
$this->display = $this->createDisplay();
// Date
$date = Carbon::now();
// Schedule the Campaign "always" onto our display
// deleting the layout will remove this at the end
$this->event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
$date->format(DateFormatHelper::getSystemFormat()),
$date->addHours(3)->format(DateFormatHelper::getSystemFormat()),
$this->campaign->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
parent::tearDown();
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Delete the Campaign
$this->sendRequest('DELETE', '/campaign/' . $this->campaign->campaignId);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCampaign;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class CampaignLayoutAssignTest
* @package Xibo\Tests\integration\Cache
*/
class CampaignLayoutAssignTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboCampaign */
protected $campaign;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboSchedule */
protected $event;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Campaign Layout Unassign Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a simple widget
$this->addSimpleWidget($layout);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Campaign
$this->campaign = (new XiboCampaign($this->getEntityProvider()))->create(Random::generateString());
// Create a Display
$this->display = $this->createDisplay();
// Date
$date = Carbon::now();
// Schedule the Campaign "always" onto our display
// deleting the layout will remove this at the end
$this->event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
$date->format(DateFormatHelper::getSystemFormat()),
$date->addHours(3)->format(DateFormatHelper::getSystemFormat()),
$this->campaign->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the Campaign
$this->campaign->delete();
parent::tearDown();
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt Done as expected');
// Add the Layout we have prepared to the existing Campaign
$this->sendRequest('POST', '/campaign/layout/assign/' . $this->campaign->campaignId, [
'layoutId' => $this->layout->layoutId
]);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt Pending as expected');
// Validate that XMR has been called.
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,155 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCampaign;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class CampaignLayoutUnassignTest
* @package Xibo\Tests\integration\Cache
*/
class CampaignLayoutUnassignTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboCampaign */
protected $campaign;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboSchedule */
protected $event;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a simple widget
$this->addSimpleWidget($layout);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Campaign
$this->campaign = (new XiboCampaign($this->getEntityProvider()))->create(Random::generateString());
// Assign the Layout to the Campaign
$this->getEntityProvider()->post('/campaign/layout/assign/' . $this->campaign->campaignId, [
'layoutId' => $this->layout->layoutId
]);
// Create a Display
$this->display = $this->createDisplay();
// Date
$date = Carbon::now();
// Schedule the Campaign "always" onto our display
// deleting the layout will remove this at the end
$this->event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
$date->format(DateFormatHelper::getSystemFormat()),
$date->addHours(3)->format(DateFormatHelper::getSystemFormat()),
$this->campaign->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the Campaign
$this->campaign->delete();
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Unassign requires edit
$this->sendRequest('PUT', '/campaign/' . $this->campaign->campaignId, [
'name' => $this->campaign->campaign,
'manageLayouts' => 1,
'layoutIds' => [] // empty list
]);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,159 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\DataSetColumn;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDataSet;
use Xibo\OAuth2\Client\Entity\XiboDataSetColumn;
use Xibo\OAuth2\Client\Entity\XiboDataSetView;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboWidget;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DataSetDataEditTest
* @package Xibo\Tests\integration\Cache
*/
class DataSetDataEditTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboDataSet */
protected $dataSet;
/** @var DataSetColumn */
protected $dataSetColumn;
/** @var XiboLayout */
protected $layout;
/** @var XiboWidget */
protected $widget;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Add a DataSet
$this->dataSet = (new XiboDataSet($this->getEntityProvider()))->create(Random::generateString(), 'Test');
// Add a Column
$this->dataSetColumn = (new XiboDataSetColumn($this->getEntityProvider()))->create($this->dataSet->dataSetId,
Random::generateString(),
'',
1,
1,
1,
'');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a couple of text widgets to the region
$response = $this->getEntityProvider()->post('/playlist/widget/datasetview/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'step' => 1,
'dataSetId' => $this->dataSet->dataSetId
]);
$this->widget = (new XiboDataSetView($this->getEntityProvider()))->hydrate($response);
// Check in
$this->layout = $this->publish($this->layout);
// Set the Layout status
$this->setLayoutStatus($this->layout, 1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the DataSet
$this->dataSet->deleteWData();
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Add Data to the DataSet
$this->sendRequest('POST','/dataset/data/'. $this->dataSet->dataSetId, [
'dataSetColumnId_' . $this->dataSetColumn->dataSetColumnId => '1'
]);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,145 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboDisplayGroup;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DisplayGroupDisplayAssignTest
* @package Xibo\Tests\integration\Cache
*/
class DisplayGroupDisplayAssignTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplayGroup */
protected $displayGroup;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a simple widget
$this->addSimpleWidget($layout);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display Group
$this->displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create(Random::generateString(), 'Cache Test', 0, null);
// Schedule the Layout "always" onto our display group
// deleting the layout will remove this at the end
$this->event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->displayGroup->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the Display Group
$this->displayGroup->delete();
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Add the Layout we have prepared to the Display Group
$response = $this->sendRequest('POST','/displaygroup/' . $this->displayGroup->displayGroupId . '/display/assign', [
'displayId' => [$this->display->displayId]
]);
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,147 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboDisplayGroup;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DisplayGroupDisplayUnassignTest
* @package Xibo\Tests\integration\Cache
*/
class DisplayGroupDisplayUnassignTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplayGroup */
protected $displayGroup;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a simple widget
$this->addSimpleWidget($layout);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display Group
$this->displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create(Random::generateString(), 'Cache Test', 0, null);
// Schedule the Layout "always" onto our display group
// deleting the layout will remove this at the end
$this->event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->displayGroup->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
// Create a Display
$this->display = $this->createDisplay();
// Assign my Display to the Group
$this->getEntityProvider()->post('/displaygroup/' . $this->displayGroup->displayGroupId . '/display/assign', [
'displayId' => [$this->display->displayId]
]);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the Display Group
$this->displayGroup->delete();
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Unassign
$this->sendRequest('POST','/displaygroup/' . $this->displayGroup->displayGroupId . '/display/unassign', [
'displayId' => [$this->display->displayId]
]);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,181 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboDisplayGroup;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DisplayGroupDynamicDisplayTest
* @package Xibo\Tests\integration\Cache
*/
class DisplayGroupDynamicDisplayTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplayGroup */
protected $displayGroup;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a simple widget
$this->addSimpleWidget($layout);
// Check us in again
$this->layout = $this->publish($this->layout);
// Create a Display Group
// this matches all displays created by the test suite
$this->displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create(
Random::generateString(),
'Cache Test',
1,
'phpunit');
$this->getLogger()->debug('DisplayGroup created with ID ' . $this->displayGroup->displayGroupId);
// Schedule the Layout "always" onto our display group
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->displayGroup->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->getLogger()->debug('Schedule created with ID ' . $event->eventId);
// Create a Display
$this->display = $this->createDisplay();
// Run regular maintenance to add the new display to our group.
$this->runRegularMaintenance();
$this->getLogger()->debug('Display created with ID ' . $this->display->displayId);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display Group
$this->displayGroup->delete();
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
$this->getLogger()->debug('Renaming display');
// Rename the display
$response = $this->sendRequest('PUT','/display/' . $this->display->displayId, [
'display' => Random::generateString(10, 'testedited'),
'defaultLayoutId' => $this->display->defaultLayoutId,
'auditingUntil' => null,
'licensed' => $this->display->licensed,
'license' => $this->display->license,
'incSchedule' => $this->display->incSchedule,
'emailAlert' => $this->display->emailAlert,
'wakeOnLanEnabled' => $this->display->wakeOnLanEnabled,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
// There isn't anything directly on the display - so that will NOT trigger anything. The schedule is on the Display Group.
$this->getLogger()->debug('Finished renaming display');
$this->assertLessThan(300, $response->getStatusCode(), 'Non-success status code, body =' . $response->getBody()->getContents());
// Initially we're expecting no change.
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Run regular maintenance
$this->runRegularMaintenance();
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Our player action would have been sent by regular maintenance, not by the edit.
// Make sure we don't have one here.
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
private function runRegularMaintenance()
{
$this->getLogger()->debug('Running Regular Maintenance');
exec('cd /var/www/cms; php bin/run.php 2');
$this->getLogger()->debug('Finished Regular Maintenance');
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Xibo\Entity\Display;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DisplayGroupLayoutAssignTest
* @package Xibo\Tests\integration\Cache
*/
class DisplayGroupLayoutAssignTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a simple widget
$this->addSimpleWidget($layout);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Add the Layout we have prepared to the Display Group
$response = $this->sendRequest('POST','/displaygroup/' . $this->display->displayGroupId . '/layout/assign', [
'layoutId' => [$this->layout->layoutId]
]);
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,120 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Xibo\Entity\Display;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DisplayGroupLayoutUnssignTest
* @package Xibo\Tests\integration\Cache
*/
class DisplayGroupLayoutUnssignTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a simple widget
$this->addSimpleWidget($layout);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display
$this->display = $this->createDisplay();
// Assign the Layout to the Display
$this->getEntityProvider()->post('/displaygroup/' . $this->display->displayGroupId . '/layout/assign', [
'layoutId' => [$this->layout->layoutId]
]);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Add the Layout we have prepared to the Display Group
$response = $this->sendRequest('POST','/displaygroup/' . $this->display->displayGroupId . '/layout/unassign', [
'layoutId' => [$this->layout->layoutId]
]);
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Xibo\Entity\Display;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DisplayGroupMediaAssignTest
* @package Xibo\Tests\integration\Cache
*/
class DisplayGroupMediaAssignTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLibrary */
protected $media;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Add a media item
$this->media = (new XiboLibrary($this->getEntityProvider()))
->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/HLH264.mp4');
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->media->deleteAssigned();
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Add the Layout we have prepared to the Display Group
$this->sendRequest('POST','/displaygroup/' . $this->display->displayGroupId . '/media/assign', [
'mediaId' => [$this->media->mediaId]
]);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,107 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Xibo\Entity\Display;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DisplayGroupMediaUnassignTest
* @package Xibo\Tests\integration\Cache
*/
class DisplayGroupMediaUnassignTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLibrary */
protected $media;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Add a media item
$this->media = (new XiboLibrary($this->getEntityProvider()))
->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/HLH264.mp4');
// Create a Display
$this->display = $this->createDisplay();
// Assign the mediaId to the display
$this->getEntityProvider()->post('/displaygroup/' . $this->display->displayGroupId . '/media/assign', [
'mediaId' => [$this->media->mediaId]
]);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->media->deleteAssigned();
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Add the Layout we have prepared to the Display Group
$this->sendRequest('POST','/displaygroup/' . $this->display->displayGroupId . '/media/unassign', [
'mediaId' => [$this->media->mediaId]
]);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,167 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class GetResourceTest
* @package Xibo\Tests\integration\Cache
*/
class GetResourceTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboTicker */
protected $widget;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a resource heavy module to the Layout (one that will download images)
$response = $this->getEntityProvider()->post('/playlist/widget/ticker/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'uri' => 'http://ceu.xibo.co.uk/mediarss/feed.xml',
'duration' => 100,
'useDuration' => 1,
'sourceId' => 1,
'templateId' => 'media-rss-with-title'
]);
// Edit the Ticker to add the template
$this->widget = (new XiboTicker($this->getEntityProvider()))->hydrate($response);
// Checkin
$this->layout = $this->publish($this->layout);
// Set the Layout status
$this->setLayoutStatus($this->layout, 3);
// Build the Layout
$this->buildLayout($this->layout);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Layout is already status 1
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layout->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
$this->assertContains('layoutid="' . $this->layout->layoutId . '"', $rf, 'Layout not in Required Files');
// Call Get Resource
$this->getLogger()->debug('Calling GetResource - for ' . $this->layout->layoutId . ' - ' . $this->layout->regions[0]->regionId . ' - ' . $this->widget->widgetId);
$this->getXmdsWrapper()->GetResource($this->display->license, $this->layout->layoutId, $this->layout->regions[0]->regionId, $this->widget->widgetId);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
}
}

View File

@@ -0,0 +1,151 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutBuildTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutBuildTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboTicker */
protected $widget;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Layout is already status 1
$this->assertTrue($this->layoutStatusEquals($this->layout, 3), 'Pre-Layout Status isnt as expected');
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Pre-Display Status isnt as expected');
// Publish (which builds)
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layout->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$object = json_decode($response->getBody(), true);
$this->layout = $this->constructLayoutFromResponse($object['data']);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Xibo\Entity\Display;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutChangeActionTest
*
* Tests whether a Layout Edit updates the Cache Appropriately
*
* @package integration\Cache
*/
class LayoutChangeActionTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Layout Edit Test');
// Create a Layout
$this->layout = $this->createLayout(1);
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure we're in good condition to start
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Edit the Layout
$this->sendRequest('POST','/displaygroup/' . $this->display->displayGroupId . '/action/changeLayout', [
'layoutId' => $this->layout->layoutId
]);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,121 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Exception\XiboApiException;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutDeleteTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutDeleteTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Layout Edit Test');
// Create a Layout
$this->layout = $this->createLayout(1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
if ($this->layout !== null)
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Delete the Layout we've got created for us.
$this->sendRequest('DELETE','/layout/' . $this->layout->layoutId);
// Check its deleted
try {
$this->layoutStatusEquals($this->layout, 0);
} catch (XiboApiException $xiboApiException) {
$this->assertEquals(404, $xiboApiException->getCode(), 'Expecting a 404, got ' . $xiboApiException->getCode());
}
$this->layout = null;
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutEditTest
*
* Tests whether a Layout Edit updates the Cache Appropriately
*
* @package integration\Cache
*/
class LayoutEditTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Layout Edit Test');
// Create a Layout
$this->layout = $this->createLayout(1);
// We need to add a widget to it, so that the Layout tests out as valid
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$this->layout = $this->publish($this->layout);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure we're in good condition to start
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Checkout this Layout
$layout = $this->checkout($this->layout);
// Validate the display status after we've checked out
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected after checkout');
// Edit the Layout
$response = $this->sendRequest('PUT','/layout/background/' . $layout->layoutId, [
'backgroundColor' => $layout->backgroundColor,
'backgroundzIndex' => $layout->backgroundzIndex
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertEquals(200, $response->getStatusCode(), 'Transaction Status Incorrect');
// Check in the Layout
$this->layout = $this->publish($this->layout);
// Validate the layout status afterwards (publish builds the layout)
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected after publish');
// Somehow test that we have issued an XMR request
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCampaign;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutInCampaignStatusTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutInCampaignStatusTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboCampaign */
protected $campaign;
/** @var XiboLayout */
protected $layout;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboTicker */
protected $widget;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Campaign
$this->campaign = (new XiboCampaign($this->getEntityProvider()))->create(Random::generateString());
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Assign the layout to our campaign
$this->getEntityProvider()->post('/campaign/layout/assign/' . $this->campaign->campaignId, [
'layoutId' => $this->layout->layoutId
]);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Campaign "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->campaign->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the Campaign
$this->campaign->delete();
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Layout is already status 1
$this->assertTrue($this->layoutStatusEquals($this->layout, 3), 'Pre-Layout Status isnt as expected');
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Pre-Display Status isnt as expected');
// Publish (which builds)
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layout->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getStatusCode() . $response->getBody()->getContents());
$response = json_decode($response->getBody(), true);
$this->layout = $this->constructLayoutFromResponse($response['data']);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Xibo\Entity\Display;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutOverlayActionTest
*
* Tests whether a Layout Edit updates the Cache Appropriately
*
* @package integration\Cache
*/
class LayoutOverlayActionTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Layout Edit Test');
// Create a Layout
$this->layout = $this->createLayout(1);
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure we're in good condition to start
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Edit the Layout
$this->sendRequest('POST','/displaygroup/' . $this->display->displayGroupId . '/action/overlayLayout', [
'layoutId' => $this->layout->layoutId
]);
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,272 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaInheritWidgetInheritTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaInheritWidgetInheritTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOff
$this->deleteLayout($this->layoutOff);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media Inherit, Widget Inherit, Output => [0, 'Inherit', 'Inherit', 0]
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media Inherit, Widget Inherit, Output => [1, 'Inherit', 'Inherit', 1]
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,282 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaInheritWidgetOffTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaInheritWidgetOffTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $mediaOn;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Set global widget enable stat set to Off
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Off');
$this->getStore()->commitIfNecessary();
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Assign the media we've edited to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOn
$this->deleteLayout($this->layoutOff);
// Delete the Display2
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
// Set global widget enable stat set to Inherit
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Inherit');
$this->getStore()->commitIfNecessary();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media Inherit, Widget Off, Output => [0, 'Inherit', 'Off', 0],
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media Inherit, Widget Off, Output => [1, 'Off', 'Off', 0],
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,280 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaInheritWidgetOnTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaInheritWidgetOnTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $mediaOn;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Set global widget enable stat set to On
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'On');
$this->getStore()->commitIfNecessary();
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Assign the media we've edited to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOn
$this->deleteLayout($this->layoutOff);
// Delete the Display2
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
// Set global widget enable stat set to Inherit
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Inherit');
$this->getStore()->commitIfNecessary();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media Inherit, Widget On, Output => [0, 'Inherit', 'On', 1],
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media Inherit, Widget On, Output => [1, 'Inherit', 'On', 1],
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,285 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaOffWidgetInheritTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaOffWidgetInheritTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $mediaOn;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Edit the media to set enableStat On
$this->media->edit(
$this->media->name,
$this->media->duration,
$this->media->retired,
$this->media->tags,
$this->media->updateInLayouts,
'Off'
);
// Assign the media we've edited to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOff
$this->deleteLayout($this->layoutOff);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media Off, Widget Inherit, Output => [0, 'Off', 'Inherit', 0],
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media Off, Widget Inherit, Output => [1, 'Off', 'Inherit', 0],
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,292 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaOffWidgetOffTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaOffWidgetOffTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $mediaOn;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Set global widget enable stat set to Off
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Off');
$this->getStore()->commitIfNecessary();
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Edit the media to set enableStat On
$this->media->edit(
$this->media->name,
$this->media->duration,
$this->media->retired,
$this->media->tags,
$this->media->updateInLayouts,
'Off'
);
// Assign the media we've edited to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOn
$this->deleteLayout($this->layoutOff);
// Delete the Display2
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
// Set global widget enable stat set to Inherit
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Inherit');
$this->getStore()->commitIfNecessary();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media Off, Widget Off, Output => [0, 'Off', 'Off', 0],
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media Off, Widget Off, Output => [1, 'Off', 'Off', 0],
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,292 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaOffWidgetOnTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaOffWidgetOnTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $mediaOn;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Set global widget enable stat set to On
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'On');
$this->getStore()->commitIfNecessary();
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Edit the media to set enableStat On
$this->media->edit(
$this->media->name,
$this->media->duration,
$this->media->retired,
$this->media->tags,
$this->media->updateInLayouts,
'Off'
);
// Assign the media we've edited to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOn
$this->deleteLayout($this->layoutOff);
// Delete the Display2
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
// Set global widget enable stat set to Inherit
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Inherit');
$this->getStore()->commitIfNecessary();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media Off, Widget On, Output => [0, 'Off', 'On', 1],
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media Off, Widget On, Output => [1, 'Off', 'On', 1],
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,283 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaOnWidgetInheritTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaOnWidgetInheritTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $mediaOn;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Edit the media to set enableStat On
$this->media->edit(
$this->media->name,
$this->media->duration,
$this->media->retired,
$this->media->tags,
$this->media->updateInLayouts,
'On'
);
// Assign the media we've edited to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOff
$this->deleteLayout($this->layoutOff);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media On, Widget Inherit, Output => [0, 'On', 'Inherit', 1],
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media On, Widget Inherit, Output => [1, 'On', 'Inherit', 1],
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,294 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaOnWidgetOffTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaOnWidgetOffTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $mediaOn;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Set global widget enable stat set to Off
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Off');
$this->getStore()->commitIfNecessary();
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Edit the media to set enableStat On
$this->media->edit(
$this->media->name,
$this->media->duration,
$this->media->retired,
$this->media->tags,
$this->media->updateInLayouts,
'On'
);
// Assign the media we've edited to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOn
$this->deleteLayout($this->layoutOff);
// Delete the Display2
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
// Set global widget enable stat set to Inherit
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Inherit');
$this->getStore()->commitIfNecessary();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media On, Widget Off, Output => [0, 'On', 'Off', 0],
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media On, Widget Off, Output => [1, 'On', 'Off', 0],
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="0" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,294 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLMediaOnWidgetOnTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLMediaOnWidgetOnTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $mediaOn;
protected $widgetId;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Set global widget enable stat set to On
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'On');
$this->getStore()->commitIfNecessary();
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
$layoutOff = $this->getDraft($this->layoutOff);
// Upload some media - enableStat is Inherit (from global media stat setting)
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(
Random::generateString(8, 'API Video'),
PROJECT_ROOT . '/tests/resources/HLH264.mp4'
);
// Edit the media to set enableStat On
$this->media->edit(
$this->media->name,
$this->media->duration,
$this->media->retired,
$this->media->tags,
$this->media->updateInLayouts,
'On'
);
// Assign the media we've edited to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOff->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
$layoutOn = $this->getDraft($this->layoutOn);
// Assign the media we've created to our regions playlist- widget with Inherit (from global widget stat setting)
$playlist2 = (new XiboPlaylist($this->getEntityProvider()))
->assign([$this->media->mediaId], 10, $layoutOn->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId2 = $playlist2->widgets[0]->widgetId;
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOn
$this->deleteLayout($this->layoutOff);
// Delete the Display2
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
// Delete the media record
$this->media->deleteAssigned();
// Set global widget enable stat set to Inherit
self::$container->get('configService')->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', 'Inherit');
$this->getStore()->commitIfNecessary();
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget With Media
// LAYOUT MEDIA WIDGET Media stats collected?
// ON ON ON YES Widget takes precedence // Match - 1
// ON OFF ON YES Widget takes precedence // Match - 1
// ON INHERIT ON YES Widget takes precedence // Match - 1
//
// OFF ON ON YES Widget takes precedence // Match - 1
// OFF OFF ON YES Widget takes precedence // Match - 1
// OFF INHERIT ON YES Widget takes precedence // Match - 1
//
// ON ON OFF NO Widget takes precedence // Match - 2
// ON OFF OFF NO Widget takes precedence // Match - 2
// ON INHERIT OFF NO Widget takes precedence // Match - 2
//
// OFF ON OFF NO Widget takes precedence // Match - 2
// OFF OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT OFF NO Widget takes precedence // Match - 2
//
// ON ON INHERIT YES Media takes precedence // Match - 3
// ON OFF INHERIT NO Media takes precedence // Match - 4
// ON INHERIT INHERIT YES Media takes precedence and Inherited from Layout // Match - 5
//
// OFF ON INHERIT YES Media takes precedence // Match - 3
// OFF OFF INHERIT NO Media takes precedence // Match - 4
// OFF INHERIT INHERIT NO Media takes precedence and Inherited from Layout // Match - 6
////
public function testLayoutOff()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
// Layout enable stat 0
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="0">', $xmlString );
// Layout Off, Media On, Widget On, Output => [0, 'On', 'On', 1],
$this->assertContains('<media id="'.$this->widgetId.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
public function testLayoutOn()
{
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
// Layout enable stat 1
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="1">', $xmlString );
// Layout On, Media On, Widget On, Output => [1, 'On', 'On', 1],
$this->assertContains('<media id="'.$this->widgetId2.'" type="video" render="native" duration="10" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="1" fileId="'.$this->media->mediaId.'">', $xmlString );
}
}

View File

@@ -0,0 +1,280 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutProofOfPlayXMLWithoutMediaTest
* @package Xibo\Tests\integration\Cache
*/
class LayoutProofOfPlayXMLWithoutMediaTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layoutOff;
/** @var XiboLayout */
protected $layoutOn;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboTicker */
protected $widget;
protected $media;
protected $widgetId2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Create a Layout with enableStat Off (by default)
$this->layoutOff = $this->createLayout();
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layoutOff will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOff->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a layout with enableStat On
$this->layoutOn = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit description',
'',
$this->getResolutionId('landscape'),
1
);
// Create a Display2
$this->display2 = $this->createDisplay();
// Schedule the LayoutOn "always" onto our display
// deleting the layoutOn will remove this at the end
$event2 = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->addSeconds(3600)->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layoutOn->campaignId,
[$this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display2, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display2);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the LayoutOff
$this->deleteLayout($this->layoutOff);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the LayoutOn
$this->deleteLayout($this->layoutOn);
// Delete the Display2
$this->deleteDisplay($this->display2);
parent::tearDown();
}
// </editor-fold>
// Logic Table
//
// Widget Without Media
// LAYOUT WIDGET Widget stats collected?
// ON ON YES Widget takes precedence // Match - 1
// ON OFF NO Widget takes precedence // Match - 2
// ON INHERIT YES Inherited from Layout // Match - 7
// OFF ON YES Widget takes precedence // Match - 1
// OFF OFF NO Widget takes precedence // Match - 2
// OFF INHERIT NO Inherited from Layout // Match - 8
/**
* Each array is a test run
* Format (enableStat)
* @return array
*/
public function layoutEnableStatOffCases()
{
return [
// Layout enableStat Off options - for layout and widget and their expected result (Widget stats collected?) in enableStat (media node attribute)
'Layout Off Media On' => [0, 'On', 1],
'Layout Off Media Off' => [0, 'Off', 0],
'Layout Off Media Inherit' => [0, 'Inherit', 0]
];
}
/**
* Each array is a test run
* Format (enableStat)
* @return array
*/
public function layoutEnableStatOnCases()
{
return [
// Layout enableStat On options - for layout and widget and their expected result (Widget stats collected?) in enableStat (media node attribute)
'Layout On Media On' => [1, 'On', 1],
'Layout On Media Off' => [1, 'Off', 0],
'Layout On Media Inherit' => [1, 'Inherit', 1]
];
}
/**
* Edit
* @dataProvider layoutEnableStatOffCases
*/
public function testLayoutOff($layoutEnableStat, $widgetEnableStat, $outputEnableStat)
{
// Checkout
$layoutOff = $this->getDraft($this->layoutOff);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layoutOff->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'] , [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1,
'enableStat' => $widgetEnableStat
]);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOff->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOff = $this->constructLayoutFromResponse($response['data']);
$this->getLogger()->debug($this->layoutOff->enableStat);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display->license);
$this->assertContains('file="' . $this->layoutOff->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display->license, $this->layoutOff->layoutId, 'layout', 0, 0);
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="'.$layoutEnableStat.'">', $xmlString );
$this->assertContains('<media id="'.$this->widget->widgetId.'" type="text" render="native" duration="100" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="'.$outputEnableStat.'">', $xmlString );
}
/**
* Edit
* @dataProvider layoutEnableStatOnCases
*/
public function testLayoutOn($layoutEnableStat, $widgetEnableStat, $outputEnableStat)
{
// Checkout
$layoutOn = $this->getDraft($this->layoutOn);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layoutOn->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'] , [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1,
'enableStat' => $widgetEnableStat
]);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Publish layout
$response = $this->sendRequest('PUT','/layout/publish/' . $this->layoutOn->layoutId, [
'publishNow' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$response = json_decode($response->getBody(), true);
$this->layoutOn = $this->constructLayoutFromResponse($response['data']);
$this->getLogger()->debug($this->layoutOn->enableStat);
// Confirm our Layout is in the Schedule
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layoutOn->layoutId . '"', $schedule, 'Layout not scheduled');
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($this->display2->license);
// Get XML string for player
$xmlString = $this->getXmdsWrapper()->GetFile($this->display2->license, $this->layoutOn->layoutId, 'layout', 0, 0);
$this->assertContains('<layout width="1920" height="1080" bgcolor="#000" schemaVersion="3" enableStat="'.$layoutEnableStat.'">', $xmlString );
$this->assertContains('<media id="'.$this->widget->widgetId.'" type="text" render="native" duration="100" useDuration="1" fromDt="1970-01-01 01:00:00" toDt="2038-01-19 03:14:07" enableStat="'.$outputEnableStat.'">', $xmlString );
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
class LayoutRetireTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Layout Retire Test');
// Create a Layout
$this->layout = $this->createLayout(1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Retire the Layout we've got created for us.
$this->sendRequest('PUT','/layout/retire/' . $this->layout->layoutId, [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
// Validate the layout status
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LibraryReviseTest
*
* Tests whether a Layout Edit updates the Cache Appropriately
*
* @package integration\Cache
*/
class LibraryReviseTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLibrary */
protected $media;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Layout Edit Test');
// Upload some media
$this->media = (new XiboLibrary($this->getEntityProvider()))
->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/xts-flowers-001.jpg');
// Create a Layout
$this->layout = $this->createLayout(1);
// Checkout
$layout = $this->getDraft($this->layout);
// Add it to the Layout
(new XiboPlaylist($this->getEntityProvider()))->assign([$this->media->mediaId], 10, $layout->regions[0]->regionPlaylist->playlistId);
// Publish
$this->layout = $this->publish($this->layout);
// Set the Layout status (force it)
$this->setLayoutStatus($this->layout, 1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->getLogger()->debug('Finished setup');
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the media record
$this->media->deleteAssigned();
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure we're in good condition to start
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout status is not as expected');
// Replace the Media
$this->media = (new XiboLibrary($this->getEntityProvider()))
->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/xts-flowers-002.jpg', $this->media->mediaId, 1, 1);
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 3), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
}
}

View File

@@ -0,0 +1,157 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class PlaylistReorderTest
* @package Xibo\Tests\integration\Cache
*/
class PlaylistReorderTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
protected $widget1;
protected $widget2;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Region Edit Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a couple of text widgets to the region
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$this->widget1 = (new XiboText($this->getEntityProvider()))->hydrate($response);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget B',
'duration' => 100,
'useDuration' => 1
]);
$this->widget2 = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Publish
$this->layout = $this->publish($this->layout);
// Set the Layout status
$this->setLayoutStatus($this->layout, 1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Checkout
$layout = $this->checkout($this->layout);
// Edit region
$this->sendRequest('POST','/playlist/order/' . $layout->regions[0]->regionPlaylist->playlistId, [
'widgets' => [
$this->widget1->widgetId => 2,
$this->widget2->widgetId => 1
]
]);
// This shouldn't effect the display
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Publish
$this->layout = $this->publish($this->layout);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
}
}

View File

@@ -0,0 +1,141 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class RegionDeleteTest
* @package Xibo\Tests\integration\Cache
*/
class RegionDeleteTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Region Delete Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a widget to the existing region
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
// Add a region to the Layout
$this->region = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,75,125);
// Set the Layout status
$this->setLayoutStatus($this->layout, 1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Edit region
$this->sendRequest('DELETE','/region/' . $this->region->regionId);
// This shouldn't effect the display
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Checkin
$this->layout = $this->publish($this->layout);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,144 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class RegionEditTest
* @package Xibo\Tests\integration\Cache
*/
class RegionEditTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboRegion */
protected $region;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Region Edit Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
// Add a widget to the existing region
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
// Set the Layout status
$this->setLayoutStatus($this->layout, 1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Edit region
$this->sendRequest('PUT','/region/' . $this->layout->regions[0]->regionId, [
'width' => 700,
'height' => 500,
'top' => 400,
'left' => 400,
'loop' => 0,
'zIndex' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Checkin
$this->layout = $this->publish($this->layout);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,159 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ScheduleChangeInsideRfTest
* @package Xibo\Tests\integration\Cache
*/
class ScheduleChangeInsideRfTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboTicker */
protected $widget;
/** @var XiboSchedule */
protected $event;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
// Check us in again
$this->layout = $this->publish($this->layout);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$this->event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Layout is already status 1
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Change the Schedule
$response = $this->sendRequest('PUT','/schedule/' . $this->event->eventId, [
'fromDt' => Carbon::createFromTimestamp($this->event->fromDt)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::createFromTimestamp($this->event->toDt)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $this->event->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertTrue(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,161 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ScheduleChangeOutsideRfTest
* @package Xibo\Tests\integration\Cache
*/
class ScheduleChangeOutsideRfTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboTicker */
protected $widget;
/** @var XiboSchedule */
protected $event;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
// Check us in again
$this->layout = $this->publish($this->layout);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display
$this->display = $this->createDisplay();
// Dates outside of RF
$date = Carbon::now()->addMonth();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$this->event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
$date->format(DateFormatHelper::getSystemFormat()),
$date->addHour()->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Make sure our Layout is already status 1
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Change the Schedule
$this->sendRequest('PUT','/schedule/' . $this->event->eventId, [
'fromDt' => date(DateFormatHelper::getSystemFormat(), $this->event->fromDt),
'toDt' => date(DateFormatHelper::getSystemFormat(), $this->event->toDt),
'eventTypeId' => 1,
'campaignId' => $this->event->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Validate that XMR has been called.
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class WidgetDeleteTest
* @package Xibo\Tests\integration\Cache
*/
class WidgetDeleteTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
protected $widget;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Region Delete Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget B',
'duration' => 100,
'useDuration' => 1
]);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Set the Layout status
$this->setLayoutStatus($this->layout, 1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Edit region
$response = $this->sendRequest('DELETE','/playlist/widget/' . $this->widget->widgetId);
$this->assertEquals(200, $response->getStatusCode(), 'Transaction Status Incorrect');
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Publish
$this->layout = $this->publish($this->layout);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,142 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Cache;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class WidgetEditTest
* @package Xibo\Tests\integration\Cache
*/
class WidgetEditTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
protected $widget;
/** @var XiboDisplay */
protected $display;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache Region Edit Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId, [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Set the Layout status
$this->setLayoutStatus($this->layout, 1);
// Create a Display
$this->display = $this->createDisplay();
// Schedule the Layout "always" onto our display
// deleting the layout will remove this at the end
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
}
public function tearDown()
{
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
/**
* @group cacheInvalidateTests
*/
public function testInvalidateCache()
{
// Edit region
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widget->widgetId, [
'text' => 'Edited Text',
'duration' => 100,
'useDuration' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertEquals(200, $response->getStatusCode(), 'Transaction Status Incorrect');
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Check us in again
$this->layout = $this->publish($this->layout);
// Check the Layout Status
// Validate the layout status afterwards
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Validate the display status afterwards
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_PENDING), 'Display Status isnt as expected');
// Somehow test that we have issued an XMR request
$this->assertFalse(in_array($this->display->displayId, $this->getPlayerActionQueue()), 'Player action not present');
}
}

View File

@@ -0,0 +1,142 @@
<?php
/*
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCampaign;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Support\Exception\InvalidArgumentException;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class CampaignLayoutManagementTest
* @package Xibo\Tests\integration
*/
class CampaignLayoutManagementTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var XiboCampaign */
protected $campaign;
/** @var XiboLayout */
protected $layout;
public function setup()
{
parent::setup();
// Create a Campaign and Layout
$this->campaign = (new XiboCampaign($this->getEntityProvider()))->create(Random::generateString());
$this->layout = $this->createLayout();
$this->layout = $this->publish($this->layout);
}
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Campaign
$this->campaign->delete();
parent::tearDown();
}
/**
* Assign Layout
*/
public function testAssignOneLayout()
{
// Assign one layout
$response = $this->sendRequest('POST', '/campaign/layout/assign/' . $this->campaign->campaignId, [
'layoutId' => $this->layout->layoutId
]);
$this->assertSame(200, $response->getStatusCode(), 'Request failed: ' . $response->getBody()->getContents());
// Get this campaign and check it has 1 layout assigned
$campaignCheck = (new XiboCampaign($this->getEntityProvider()))->getById($this->campaign->campaignId);
$this->assertSame($this->campaign->campaignId, $campaignCheck->campaignId, $response->getBody());
$this->assertSame(1, $campaignCheck->numberLayouts, $response->getBody());
}
/**
* Assign Layout
*/
public function testAssignTwoLayouts()
{
$response = $this->sendRequest('PUT', '/campaign/' . $this->campaign->campaignId, [
'name' => $this->campaign->campaign,
'manageLayouts' => 1,
'layoutIds' => [$this->layout->layoutId, $this->layout->layoutId]
]);
$this->assertSame(200, $response->getStatusCode(), 'Request failed');
// Get this campaign and check it has 2 layouts assigned
$campaignCheck = (new XiboCampaign($this->getEntityProvider()))->getById($this->campaign->campaignId);
$this->assertSame($this->campaign->campaignId, $campaignCheck->campaignId, $response->getBody());
$this->assertSame(2, $campaignCheck->numberLayouts, $response->getBody());
}
/**
* Unassign Layout
*/
public function testUnassignLayout()
{
$this->getEntityProvider()->post('/campaign/layout/assign/' . $this->campaign->campaignId, [
'layoutId' => $this->layout->layoutId
]);
$response = $this->sendRequest('PUT', '/campaign/' . $this->campaign->campaignId, [
'name' => $this->campaign->campaign,
'manageLayouts' => 1,
'layoutIds' => []
]);
$campaignCheck = (new XiboCampaign($this->getEntityProvider()))->getById($this->campaign->campaignId);
$this->assertSame($this->campaign->campaignId, $campaignCheck->campaignId, $response->getBody());
$this->assertSame(0, $campaignCheck->numberLayouts, $response->getBody());
}
/**
* Assign Layout to layout specific campaignId - expect failure
* @throws \Exception
*/
public function testAssignLayoutFailure()
{
// Call assign on the layout specific campaignId
$request = $this->createRequest('POST', '/campaign/layout/assign/' . $this->layout->campaignId);
$request = $request->withParsedBody([
'layoutId' => $this->layout->layoutId
]);
try {
$this->app->handle($request);
} catch (InvalidArgumentException $exception) {
$this->assertSame(422, $exception->getCode(), 'Expecting failure, received ' . $exception->getMessage());
}
}
}

View File

@@ -0,0 +1,216 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCampaign;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboResolution;
use Xibo\Tests\LocalWebTestCase;
/**
* Class CampaignTest
* @package Xibo\Tests
*/
class CampaignTest extends LocalWebTestCase
{
protected $startCampaigns;
protected $startLayouts;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startCampaigns = (new XiboCampaign($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->startLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all campaigns that weren't there initially
$finalCamapigns = (new XiboCampaign($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining campaigns and nuke them
foreach ($finalCamapigns as $campaign) {
/** @var XiboCampaign $campaign */
$flag = true;
foreach ($this->startCampaigns as $startCampaign) {
if ($startCampaign->campaignId == $campaign->campaignId) {
$flag = false;
}
}
if ($flag) {
try {
$campaign->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $campaign->campaignId . '. E:' . $e->getMessage());
}
}
}
// tearDown all layouts that weren't there initially
$finalLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining layouts and nuke them
foreach ($finalLayouts as $layout) {
/** @var XiboLayout $layout */
$flag = true;
foreach ($this->startLayouts as $startLayout) {
if ($startLayout->layoutId == $layout->layoutId) {
$flag = false;
}
}
if ($flag) {
try {
$layout->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $layout->layoutId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* @param $type
* @return int
*/
private function getResolutionId($type)
{
if ($type === 'landscape') {
$width = 1920;
$height = 1080;
} else if ($type === 'portrait') {
$width = 1080;
$height = 1920;
} else {
return -10;
}
//$this->getLogger()->debug('Querying for ' . $width . ', ' . $height);
$resolutions = (new XiboResolution($this->getEntityProvider()))->get(['width' => $width, 'height' => $height]);
if (count($resolutions) <= 0)
return -10;
return $resolutions[0]->resolutionId;
}
/**
* Show Campaigns
*/
public function testListAll()
{
# Get list of all campaigns
$response = $this->sendRequest('GET', '/campaign');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$object = json_decode($response->getBody());
# Check if call was successful
$this->assertObjectHasAttribute('data', $object);
$this->assertNotEmpty($object->data);
}
/**
* Add Campaign
*/
public function testAdd()
{
# Generate random name
$name = Random::generateString(8, 'phpunit');
# Add campaign
$response = $this->sendRequest('POST', '/campaign', ['name' => $name]);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if campaign has he name we want it to have
$this->assertSame($name, $object->data->campaign);
}
/**
* Test edit
*/
public function testEdit()
{
# Generate name and add campaign
$name = Random::generateString(8, 'phpunit');
$campaign = (new XiboCampaign($this->getEntityProvider()))->create($name);
# Generate new random name
$newName = Random::generateString(8, 'phpunit');
# Edit the campaign we added and change the name
$response = $this->sendRequest('PUT', '/campaign/' . $campaign->campaignId, ['name' => $newName]);
# check if cal was successful
$this->assertSame(200, $response->getStatusCode());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
# Check if campaign has the new name now
$this->assertSame($newName, $object->data->campaign);
}
/**
* Test Delete
*/
public function testDelete()
{
# generate two random names
$name1 = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
# Load in a couple of known campaigns
$camp1 = (new XiboCampaign($this->getEntityProvider()))->create($name1);
$camp2 = (new XiboCampaign($this->getEntityProvider()))->create($name2);
# Delete the one we created last
$response = $this->sendRequest('DELETE', '/campaign/' . $camp2->campaignId);
# This should return 204 for success
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Check only one remains
$campaigns = (new XiboCampaign($this->getEntityProvider()))->get();
$this->assertEquals(count($this->startCampaigns) + 1, count($campaigns));
$flag = false;
foreach ($campaigns as $campaign) {
if ($campaign->campaignId == $camp1->campaignId) {
$flag = true;
}
}
# Check if everything is in order
$this->assertTrue($flag, 'Campaign ID ' . $camp1->campaignId . ' was not found after deleting a different campaign');
# Cleanup
$camp1->delete();
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
class ClockTest extends \Xibo\Tests\LocalWebTestCase
{
public function testView()
{
$response = $this->sendRequest('GET','/clock');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$response = json_decode($response->getBody());
$this->assertNotEmpty($response->data);
}
}

View File

@@ -0,0 +1,276 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCommand;
use Xibo\Tests\LocalWebTestCase;
class CommandTest extends LocalWebTestCase
{
protected $startCommands;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startCommands = (new XiboCommand($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all commands that weren't there initially
$finalCommands = (new XiboCommand($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining commands and nuke them
foreach ($finalCommands as $command) {
/** @var XiboCommand $command */
$flag = true;
foreach ($this->startCommands as $startCom) {
if ($startCom->commandId == $command->commandId) {
$flag = false;
}
}
if ($flag) {
try {
$command->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $command->commandId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* Shows this user commands
*/
public function testListAll()
{
# Get the list of all commands
$response = $this->sendRequest('GET','/command');
# Check if call was successful
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
/**
* testAddSuccess - test adding various commands that should be valid
* @dataProvider provideSuccessCases
* @group minimal
*/
public function testAddSuccess($commandName, $commandDescription, $commandCode)
{
// Loop through any pre-existing commands to make sure we're not
// going to get a clash
foreach ($this->startCommands as $tmpCom) {
if ($tmpCom->command == $commandName) {
$this->skipTest("There is a pre-existing command with this name");
return;
}
}
# Add new comands with arguments from provideSuccessCases
$response = $this->sendRequest('POST','/command', [
'command' => $commandName,
'description' => $commandDescription,
'code' => $commandCode
]);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
# Check if commands were added successfully and have correct parameters
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($commandName, $object->data->command);
$this->assertSame($commandDescription, $object->data->description);
$this->assertSame($commandCode, $object->data->code);
# Check again that the command was added correctly
$command = (new XiboCommand($this->getEntityProvider()))->getById($object->id);
$this->assertSame($commandName, $command->command);
$this->assertSame($commandDescription, $command->description);
$this->assertSame($commandCode, $command->code);
# Clean up the commands as we no longer need it
$this->assertTrue($command->delete(), 'Unable to delete ' . $command->commandId);
}
/**
* Each array is a test run
* Format (command name, description, code)
* @return array
*/
public function provideSuccessCases()
{
# Cases we provide to testAddSuccess, you can extend it by simply adding new case here
return [
'reboot' => ['test command', 'test description', 'reboot'],
'binary' => ['test command 2', '|01100100|01100001|01101110|00001101', 'binary'],
'sleep' => ['test command 3', 'test description', 'sleep'],
];
}
/**
* testAddFailure - test adding various commands that should be invalid
* @dataProvider provideFailureCases
*/
public function testAddFailure($commandName, $commandDescription, $commandCode)
{
# Add new commands with arguments from provideFailureCases
$response = $this->sendRequest('POST','/command', [
'command' => $commandName,
'description' => $commandDescription,
'code' => $commandCode
]);
# Check if commands are failing as expected
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* Each array is a test run
* Format (command name, description, code)
* @return array
*/
public function provideFailureCases()
{
# Cases we provide to testAddFailure, you can extend it by simply adding new case here
return [
'No code' => ['No code', 'aa', NULL],
'Code with space' => ['Code with space', 'Code with space', 'Code with space'],
'Code with symbol' => ['Code with symbol', 'Code with symbol', 'Codewithsymbol$$'],
'No description' => ['no description', NULL, 'code'],
'No Name' => [NULL, 'Bienvenue à la suite de tests Xibo', 'beep'],
'Only Name' => ['Deutsch Prüfung 1', NULL, NULL],
'Empty' => [NULL, NULL, NULL]
];
}
/**
* List all commands known set
* @group minimal
* @depends testAddSuccess
*/
public function testListKnown()
{
$cases = $this->provideSuccessCases();
$commands = [];
// Check each possible case to ensure it's not pre-existing
// If it is, skip over it
foreach ($cases as $case) {
$flag = true;
foreach ($this->startCommands as $tmpCom) {
if ($case[0] == $tmpCom->command) {
$flag = false;
}
}
if ($flag) {
$commands[] = (new XiboCommand($this->getEntityProvider()))->create($case[0],$case[1],$case[2]);
}
}
$response = $this->sendRequest('GET','/command');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# There should be as many commands as we created plus the number we started with in the system
$this->assertEquals(count($commands) + count($this->startCommands), $object->data->recordsTotal);
# Clean up the groups we created
foreach ($commands as $com) {
$com->delete();
}
}
/**
* Edit an existing command
*/
public function testEdit()
{
# Load in a known command
/** @var XiboCommand $command */
$command = (new XiboCommand($this->getEntityProvider()))->create('phpunit command', 'phpunit description', 'phpunitcode');
# Generate new name and description
$name = Random::generateString(8, 'command');
$description = Random::generateString(8, 'description');
# Change name and description of earlier created command
$response = $this->sendRequest('PUT','/command/' . $command->commandId, [
'command' => $name,
'description' => $description,
'code' => $command->code
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
# Examine the returned object and check that it's what we expect
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->command);
$this->assertSame($description, $object->data->description);
# Check that the command name and description were actually renamed
$command = (new XiboCommand($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $command->command);
$this->assertSame($description, $command->description);
# Clean up the Layout as we no longer need it
$command->delete();
}
/**
* Test delete
* @group minimal
*/
public function testDelete()
{
# Generate random names
$name1 = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
# Load in a couple of known commands
$command1 = (new XiboCommand($this->getEntityProvider()))->create($name1, 'phpunit description', 'code');
$command2 = (new XiboCommand($this->getEntityProvider()))->create($name2, 'phpunit description', 'codetwo');
# Delete the one we created last
$response = $this->sendRequest('DELETE','/command/' . $command2->commandId);
# This should return 204 for success
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Check only one remains
$commands = (new XiboCommand($this->getEntityProvider()))->get();
$this->assertEquals(count($this->startCommands) + 1, count($commands));
$flag = false;
foreach ($commands as $command) {
if ($command->commandId == $command1->commandId) {
$flag = true;
}
}
$this->assertTrue($flag, 'Command ID ' . $command1->commandId . ' was not found after deleting a different command');
# Clean up the first command as we no longer need it
$command1->delete();
}
}

View File

@@ -0,0 +1,161 @@
<?php
/**
* Copyright (C) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDataSet;
use Xibo\Tests\LocalWebTestCase;
/**
* Test remote datasets
*/
class DataSetRemoteTest extends LocalWebTestCase
{
/** @var \Xibo\OAuth2\Client\Entity\XiboDataSet */
private $dataSet;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// copy json file to /web folder
shell_exec('cp -r ' . PROJECT_ROOT . '/tests/resources/RemoteDataSet.json ' . PROJECT_ROOT . '/web');
$this->dataSet = (new XiboDataSet($this->getEntityProvider()))
->create(
Random::generateString(8, 'phpunit'),
'',
'remote',
1,
'GET',
'http://localhost/RemoteDataSet.json',
'',
'',
'',
'',
1,
0,
null,
'data'
);
// Add columns
$this->dataSet->createColumn(
'title',
null,
1,
1,
3,
null,
'title'
);
$this->dataSet->createColumn(
'identifier',
null,
2,
2,
3,
null,
'id'
);
$this->dataSet->createColumn(
'date',
null,
3,
3,
3,
null,
'Date'
);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the dataset
$this->dataSet->deleteWData();
// remove json file from /web folder
shell_exec('rm -r ' . PROJECT_ROOT . '/web/RemoteDataSet.json');
parent::tearDown();
}
public function testRemoteDataSetData()
{
// call the remote dataSet test
$response = $this->sendRequest('POST', '/dataset/remote/test', [
'testDataSetId' => $this->dataSet->dataSetId,
'dataSet' => $this->dataSet->dataSet,
'code' => 'remote',
'isRemote' => 1,
'method' => 'GET',
'uri' => 'http://localhost/RemoteDataSet.json',
'dataRoot' => 'data',
'refreshRate' => 0,
'clearRate' => 1,
'sourceId' => 1,
'limitPolicy' => 'stop'
]);
// HTTP response code
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
// Expect a JSON body
$object = json_decode($response->getBody());
// Data and ID parameters
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
// Make sure we have the same dataset back
$this->assertSame($object->id, $this->dataSet->dataSetId);
// Make sure we parsed out some entries.
$this->assertNotEmpty($object->data->entries);
$this->assertNotEmpty($object->data->processed);
// The entries should match our sample file.
$this->assertSame(3, $object->data->number);
// First record
$this->assertSame(1, $object->data->processed[0][0]->identifier);
$this->assertSame('Title 1', $object->data->processed[0][0]->title);
$this->assertSame('2019-07-29 13:11:00', $object->data->processed[0][0]->date);
// Second record
$this->assertSame(2, $object->data->processed[0][1]->identifier);
$this->assertFalse(property_exists($object->data->processed[0][1], 'title'));
$this->assertSame('2019-07-30 03:04:00', $object->data->processed[0][1]->date);
// Third record
$this->assertSame(3, $object->data->processed[0][2]->identifier);
$this->assertSame('1', $object->data->processed[0][2]->title);
$this->assertFalse(property_exists($object->data->processed[0][2], 'date'));
}
}

View File

@@ -0,0 +1,546 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDataSet;
use Xibo\OAuth2\Client\Entity\XiboDataSetColumn;
use Xibo\OAuth2\Client\Entity\XiboDataSetRow;
use Xibo\Tests\LocalWebTestCase;
class DataSetTest extends LocalWebTestCase
{
protected $startDataSets;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startDataSets = (new XiboDataSet($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all datasets that weren't there initially
$finalDataSets = (new XiboDataSet($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$difference = array_udiff($finalDataSets, $this->startDataSets, function ($a, $b) {
/** @var XiboDataSet $a */
/** @var XiboDataSet $b */
return $a->dataSetId - $b->dataSetId;
});
# Loop over any remaining datasets and nuke them
foreach ($difference as $dataSet) {
/** @var XiboDataSet $dataSet */
try {
$dataSet->deleteWData();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $dataSet->dataSetId . '. E: ' . $e->getMessage() . PHP_EOL);
}
}
parent::tearDown();
}
/*
* List all datasets
*/
public function testListAll()
{
$response = $this->sendRequest('GET','/dataset');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
/**
* @group add
*/
public function testAdd()
{
# Generate random name
$name = Random::generateString(8, 'phpunit');
# Add dataset
$response = $this->sendRequest('POST','/dataset', [
'dataSet' => $name,
'description' => 'PHP Unit Test'
]);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if dataset has the correct name
$this->assertSame($name, $object->data->dataSet);
}
/**
* Test edit
* @depends testAdd
*/
public function testEdit()
{
# Create a new dataset
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create('phpunit dataset', 'phpunit description');
# Generate new name and description
$name = Random::generateString(8, 'phpunit');
$description = 'New description';
# Edit the name and description
$response = $this->sendRequest('PUT','/dataset/' . $dataSet->dataSetId, [
'dataSet' => $name,
'description' => $description
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
# Check if name and description were correctly changed
$this->assertSame($name, $object->data->dataSet);
$this->assertSame($description, $object->data->description);
# Deeper check by querying for dataset again
$dataSetCheck = (new XiboDataSet($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $dataSetCheck->dataSet);
$this->assertSame($description, $dataSetCheck->description);
# Clean up the dataset as we no longer need it
$dataSet->delete();
}
/**
* @depends testEdit
*/
public function testDelete()
{
# Generate new random names
$name1 = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
# Load in a couple of known dataSets
$data1 = (new XiboDataSet($this->getEntityProvider()))->create($name1, 'phpunit description');
$data2 = (new XiboDataSet($this->getEntityProvider()))->create($name2, 'phpunit description');
# Delete the one we created last
$response = $this->sendRequest('DELETE','/dataset/' . $data2->dataSetId);
# This should return 204 for success
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Check only one remains
$dataSets = (new XiboDataSet($this->getEntityProvider()))->get();
$this->assertEquals(count($this->startDataSets) + 1, count($dataSets));
$flag = false;
foreach ($dataSets as $dataSet) {
if ($dataSet->dataSetId == $data1->dataSetId) {
$flag = true;
}
}
$this->assertTrue($flag, 'dataSet ID ' . $data1->dataSetId . ' was not found after deleting a different dataset');
}
# TO DO /dataset/import/
/**
* @dataProvider provideSuccessCases
*/
public function testAddColumnSuccess($columnName, $columnListContent, $columnOrd, $columnDataTypeId, $columnDataSetColumnTypeId, $columnFormula)
{
# Create radom name and description
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit column add';
# Create new dataset
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
# Create new columns with arguments from provideSuccessCases
$response = $this->sendRequest('POST','/dataset/' . $dataSet->dataSetId . '/column', [
'heading' => $columnName,
'listContent' => $columnListContent,
'columnOrder' => $columnOrd,
'dataTypeId' => $columnDataTypeId,
'dataSetColumnTypeId' => $columnDataSetColumnTypeId,
'formula' => $columnFormula
]);
# Check that call was successful
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
# Check that columns have correct parameters
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($columnName, $object->data->heading);
$this->assertSame($columnListContent, $object->data->listContent);
$this->assertSame($columnOrd, $object->data->columnOrder);
$this->assertSame($columnDataTypeId, $object->data->dataTypeId);
$this->assertSame($columnDataSetColumnTypeId, $object->data->dataSetColumnTypeId);
$this->assertSame($columnFormula, $object->data->formula);
# Check that column was correctly added
$column = (new XiboDataSetColumn($this->getEntityProvider()))->getById($dataSet->dataSetId, $object->id);
$this->assertSame($columnName, $column->heading);
# Clean up the dataset as we no longer need it
$this->assertTrue($dataSet->delete(), 'Unable to delete ' . $dataSet->dataSetId);
}
/**
* Each array is a test run
* Format ($columnName, $columnListContent, $columnOrd, $columnDataTypeId, $columnDataSetColumnTypeId, $columnFormula)
* @return array
*/
public function provideSuccessCases()
{
# Cases we provide to testAddColumnSucess, you can extend it by simply adding new case here
return [
# Value
'Value String' => ['Test Column Value String', NULL, 2, 1, 1, NULL],
'List Content' => ['Test Column list content', 'one,two,three', 2, 1, 1, NULL],
'Value Number' => ['Test Column Value Number', NULL, 2, 2, 1, NULL],
'Value Date' => ['Test Column Value Date', NULL, 2, 3, 1, NULL],
'External Image' => ['Test Column Value External Image', NULL, 2, 4, 1, NULL],
'Library Image' => ['Test Column Value Internal Image', NULL, 2, 5, 1, NULL],
# Formula
'Formula' => ['Test Column Formula', NULL, 2, 5, 1, 'Where Name = Dan'],
];
}
/**
* @dataProvider provideFailureCases
*/
public function testAddColumnFailure($columnName, $columnListContent, $columnOrd, $columnDataTypeId, $columnDataSetColumnTypeId, $columnFormula)
{
# Create random name and description
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit column add failure';
# Create new columns that we expect to fail with arguments from provideFailureCases
/** @var XiboDataSet $dataSet */
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
$response = $this->sendRequest('POST','/dataset/' . $dataSet->dataSetId . '/column', [
'heading' => $columnName,
'listContent' => $columnListContent,
'columnOrder' => $columnOrd,
'dataTypeId' => $columnDataTypeId,
'dataSetColumnTypeId' => $columnDataSetColumnTypeId,
'formula' => $columnFormula
]);
# Check if cases are failing as expected
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* Each array is a test run
* Format ($columnName, $columnListContent, $columnOrd, $columnDataTypeId, $columnDataSetColumnTypeId, $columnFormula)
* @return array
*/
public function provideFailureCases()
{
# Cases we provide to testAddColumnFailure, you can extend it by simply adding new case here
return [
// Value
'Incorrect dataType' => ['incorrect data type', NULL, 2, 12, 1, NULL],
'Incorrect columnType' => ['incorrect column type', NULL, 2, 19, 1, NULL],
'Empty Name' => [NULL, NULL, 2, 3, 1, NULL],
'Symbol Name' => ['a.b.c', NULL, 2, 3, 1, NULL],
'Symbol Name 2' => ['$£"', NULL, 2, 3, 1, NULL]
];
}
/**
* Search columns for DataSet
*/
public function testListAllColumns()
{
# Create new dataSet
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit column list';
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
# Add a new column to our dataset
$nameCol = Random::generateString(8, 'phpunit');
$dataSet->createColumn($nameCol,'', 2, 1, 1, '');
# Search for columns
$response = $this->sendRequest('GET','/dataset/' . $dataSet->dataSetId . '/column');
# Check if call was successful
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# Clean up as we no longer need it
$dataSet->delete();
}
/**
* Test edit column
*/
public function testColumnEdit()
{
# Create dataSet
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit column edit';
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
# Add new column to our dataset
$nameCol = Random::generateString(8, 'phpunit');
$column = (new XiboDataSetColumn($this->getEntityProvider()))->create($dataSet->dataSetId, $nameCol,'', 2, 1, 1, '');
# Generate new random name
$nameNew = Random::generateString(8, 'phpunit');
# Edit our column and change the name
$response = $this->sendRequest('PUT','/dataset/' . $dataSet->dataSetId . '/column/' . $column->dataSetColumnId, [
'heading' => $nameNew,
'listContent' => '',
'columnOrder' => $column->columnOrder,
'dataTypeId' => $column->dataTypeId,
'dataSetColumnTypeId' => $column->dataSetColumnTypeId,
'formula' => ''
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if our column has updated name
$this->assertSame($nameNew, $object->data->heading);
# Clean up as we no longer need it
$dataSet->delete();
}
/**
* @param $dataSetId
* @depends testAddColumnSuccess
*/
public function testDeleteColumn()
{
# Create dataSet
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit column delete';
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
# Add new column to our dataset
$nameCol = Random::generateString(8, 'phpunit');
$column = (new XiboDataSetColumn($this->getEntityProvider()))->create($dataSet->dataSetId, $nameCol,'', 2, 1, 1, '');
# delete column
$response = $this->sendRequest('DELETE','/dataset/' . $dataSet->dataSetId . '/column/' . $column->dataSetColumnId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
}
/*
* GET data
*/
public function testGetData()
{
# Create dataSet
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit';
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
# Call get data
$response = $this->sendRequest('GET','/dataset/data/' . $dataSet->dataSetId);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# Clean up
$dataSet->delete();
}
/**
* Test add row
*/
public function testRowAdd()
{
# Create a new dataset to use
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit row add';
/** @var XiboDataSet $dataSet */
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
# Create column and add it to our dataset
$nameCol = Random::generateString(8, 'phpunit');
$column = (new XiboDataSetColumn($this->getEntityProvider()))->create($dataSet->dataSetId, $nameCol,'', 2, 1, 1, '');
# Add new row to our dataset and column
$response = $this->sendRequest('POST','/dataset/data/' . $dataSet->dataSetId, [
'dataSetColumnId_' . $column->dataSetColumnId => 'test',
]);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Get the row id
$row = $dataSet->getData();
$this->getLogger()->debug(json_encode($row));
# Check if data was correctly added to the row
$this->assertArrayHasKey($nameCol, $row[0]);
$this->assertSame($row[0][$nameCol], 'test');
# Clean up as we no longer need it, deleteWData will delete dataset even if it has data assigned to it
$dataSet->deleteWData();
}
/**
* Test edit row
* @dataProvider provideSuccessCasesRow
*/
public function testRowEdit($data)
{
# Create a new dataset to use
/** @var XiboDataSet $dataSet */
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit row edit';
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
# Generate a new name for the new column
$nameCol = Random::generateString(8, 'phpunit');
# Create new column and add it to our dataset
$column = (new XiboDataSetColumn($this->getEntityProvider()))->create($dataSet->dataSetId, $nameCol,'', 2, 1, 1, '');
# Add new row with data to our dataset
$rowD = 'test';
$row = (new XiboDataSetRow($this->getEntityProvider()))->create($dataSet->dataSetId, $column->dataSetColumnId, $rowD);
# Edit row data
$response = $this->sendRequest('PUT','/dataset/data/' . $dataSet->dataSetId . '/' . $row['id'], [
'dataSetColumnId_' . $column->dataSetColumnId => $data
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# get the row id
$rowCheck = $dataSet->getData();
# Check if data was correctly added to the row
$this->assertArrayHasKey($nameCol, $rowCheck[0]);
if ($data == Null){
$this->assertSame($rowCheck[0][$nameCol], $rowD);
}
else {
$this->assertSame($rowCheck[0][$nameCol], $data);
}
# Clean up as we no longer need it, deleteWData will delete dataset even if it has data assigned to it
$dataSet->deleteWData();
}
/**
* Each array is a test run
* Format ($data)
* @return array
*/
public function provideSuccessCasesRow()
{
# Cases we provide to testRowEdit, you can extend it by simply adding new case here
return [
# Value
'String' => ['API EDITED ROW'],
'Null' => [NULL],
'number as string' => ['1212']
];
}
/*
* delete row data
*/
public function testRowDelete()
{
# Create a new dataset to use
/** @var XiboDataSet $dataSet */
$name = Random::generateString(8, 'phpunit');
$description = 'PHP Unit row delete';
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, $description);
# Generate a new name for the new column
$nameCol = Random::generateString(8, 'phpunit');
# Create new column and add it to our dataset
$column = (new XiboDataSetColumn($this->getEntityProvider()))->create($dataSet->dataSetId, $nameCol,'', 2, 1, 1, '');
# Add new row data
$row = (new XiboDataSetRow($this->getEntityProvider()))->create($dataSet->dataSetId, $column->dataSetColumnId, 'Row Data');
# Delete row
$response = $this->sendRequest('DELETE','/dataset/data/' . $dataSet->dataSetId . '/' . $row['id']);
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up as we no longer need it, deleteWData will delete dataset even if it has data assigned to it
$dataSet->deleteWData();
}
public function testAddRemoteDataSet()
{
$name = Random::generateString(8, 'phpunit');
# Add dataset
$response = $this->sendRequest('POST','/dataset', [
'dataSet' => $name,
'code' => 'remote',
'isRemote' => 1,
'method' => 'GET',
'uri' => 'http://localhost/resources/RemoteDataSet.json',
'dataRoot' => 'data',
'refreshRate' => 0,
'clearRate' => 1,
'sourceId' => 1,
'limitPolicy' => 'stop'
]);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check dataSet object
$this->assertSame($name, $object->data->dataSet);
$this->assertSame(1, $object->data->isRemote);
$this->assertSame('http://localhost/resources/RemoteDataSet.json', $object->data->uri);
$this->assertSame(1, $object->data->clearRate);
$this->assertSame(0, $object->data->refreshRate);
$this->assertSame(0, $object->data->lastClear);
$this->assertSame(1, $object->data->sourceId);
}
public function testEditRemoteDataSet()
{
$name = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
// add DataSet with wrapper
$dataSet = (new XiboDataSet($this->getEntityProvider()))->create($name, '', 'remote', 1, 'GET', 'http://localhost/resources/RemoteDataSet.json', '', '', '', '', 1, 0, null, 'data');
// Edit DataSet
$response = $this->sendRequest('PUT','/dataset/' . $dataSet->dataSetId, [
'dataSet' => $name2,
'code' => 'remote',
'isRemote' => 1,
'method' => 'GET',
'uri' => 'http://localhost/resources/RemoteDataSet.json',
'dataRoot' => 'data',
'clearRate' => 3600,
'refreshRate' => 1,
'sourceId' => 1,
'limitPolicy' => 'stop'
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check dataSet object
$this->assertSame($name2, $object->data->dataSet);
$this->assertSame(1, $object->data->isRemote);
$this->assertSame('http://localhost/resources/RemoteDataSet.json', $object->data->uri);
$this->assertSame(3600, $object->data->clearRate);
$this->assertSame(1, $object->data->refreshRate);
$this->assertSame(1, $object->data->sourceId);
}
}

View File

@@ -0,0 +1,223 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDaypart;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DaypartTest
* @package Xibo\Tests\Integration
*/
class DaypartTest extends LocalWebTestCase
{
/** @var XiboDaypart[] */
protected $startDayparts;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startDayparts = (new XiboDaypart($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->getLogger()->debug('There are ' . count($this->startDayparts) . ' dayparts at the start of the test');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all dayparts that weren't there initially
$finalDayparts = (new XiboDaypart($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining dayparts and nuke them
foreach ($finalDayparts as $daypart) {
/** @var XiboDaypart $daypart */
$flag = true;
foreach ($this->startDayparts as $startDaypart) {
if ($startDaypart->dayPartId == $daypart->dayPartId) {
$flag = false;
}
}
if ($flag) {
try {
$daypart->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $daypart->dayPartId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* testAddSuccess - test adding various daypart that should be valid
* @dataProvider provideSuccessCases
*/
public function testAddSuccess($name, $description, $startTime, $endTime, $exceptionDays, $exceptionStartTimes, $exceptionEndTimes)
{
# Create daypart with arguments from provideSuccessCases
$response = $this->sendRequest('POST','/daypart', [
'name' => $name,
'description' => $description,
'startTime' => $startTime,
'endTime' => $endTime,
'exceptionDays' => $exceptionDays,
'exceptionStartTimes' => $exceptionStartTimes,
'exceptionEndTimes' => $exceptionEndTimes
]);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame($description, $object->data->description);
# Check that the daypart was really added
$dayparts = (new XiboDaypart($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->assertEquals(count($this->startDayparts) + 1, count($dayparts));
# Check that the daypart was added correctly
$daypart = (new XiboDaypart($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $daypart->name);
$this->assertSame($description, $daypart->description);
# Clean up the daypart as we no longer need it
$this->assertTrue($daypart->delete(), 'Unable to delete ' . $daypart->dayPartId);
}
/**
* testAddFailure - test adding various daypart that should be invalid
* @dataProvider provideFailureCases
*/
public function testAddFailure($name, $description, $startTime, $endTime, $exceptionDays, $exceptionStartTimes, $exceptionEndTimes)
{
# Create daypart with arguments from provideFailureCases
$response = $this->sendRequest('POST','/daypart', [
'name' => $name,
'description' => $description,
'startTime' => $startTime,
'endTime' => $endTime,
'exceptionDays' => $exceptionDays,
'exceptionStartTimes' => $exceptionStartTimes,
'exceptionEndTimes' => $exceptionEndTimes
]);
# check if they fail as expected
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* Each array is a test run
* Format ($name, $description, $startTime, $endTime, $exceptionDays, $exceptionStartTimes, $exceptionEndTimes)
* @return array
*/
public function provideSuccessCases()
{
# Data for testAddSuccess, easily expandable - just add another set of data below
return [
'No exceptions' => ['phpunit daypart', 'API', '02:00', '06:00', NULL, NULL, NULL],
'Except Monday' => ['phpunit daypart exception', NULL, '02:00', '06:00', ['Monday'], ['00:01'], ['23:59']]
];
}
/**
* Each array is a test run
* Format ($name, $description, $startTime, $endTime, $exceptionDays, $exceptionStartTimes, $exceptionEndTimes)
* @return array
*/
public function provideFailureCases()
{
# Data for testAddfailure, easily expandable - just add another set of data below
// TODO we should probably validate description and day names in daypart Controller.
return [
'Empty title' => [NULL, 'should be invalid', '07:00', '10:00', NULL, NULL, NULL],
//'Description over 254 characters' => ['Too long description', Random::generateString(258), '07:00', '10:00', NULL, NULL, NULL],
'Wrong time data type' => ['Time as integer','should be incorrect', 21, 22, NULL, NULL, NULL],
//'Wrong day name' => ['phpunit daypart exception', NULL, '02:00', '06:00', ['Cabbage'], ['00:01'], ['23:59']]
];
}
/**
* Edit an existing daypart
*/
public function testEdit()
{
#Create new daypart
$daypart = (new XiboDaypart($this->getEntityProvider()))->create('phpunit daypart', 'API', '02:00', '06:00', NULL, NULL, NULL);
# Change the daypart name and description
$name = Random::generateString(8, 'phpunit');
$description = Random::generateString(8, 'description');
$response = $this->sendRequest('PUT','/daypart/' . $daypart->dayPartId, [
'name' => $name,
'description' => $description,
'startTime' => '02:00',
'endTime' => '06:00',
'exceptionDays' => $daypart->exceptionDays,
'exceptionStartTimes' => $daypart->exceptionStartTimes,
'exceptionEndTimes' => $daypart->exceptionEndTimes
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
# Examine the returned object and check that it's what we expect
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame($description, $object->data->description);
# Check that the daypart was actually renamed
$daypart = (new XiboDaypart($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $daypart->name);
$this->assertSame($description, $daypart->description);
# Clean up the Daypart as we no longer need it
$daypart->delete();
}
/**
* Test delete
* @group minimal
*/
public function testDelete()
{
$name1 = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
# Load in a couple of known Dayparts
$daypart1 = (new XiboDaypart($this->getEntityProvider()))->create($name1, 'API', '02:00', '06:00', NULL, NULL, NULL);
$daypart2 = (new XiboDaypart($this->getEntityProvider()))->create($name2, 'API', '12:00', '16:00', NULL, NULL, NULL);
# Delete the one we created last
$response = $this->sendRequest('DELETE','/daypart/' . $daypart2->dayPartId);
# This should return 204 for success
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Check only one remains
$dayparts = (new XiboDaypart($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->assertEquals(count($this->startDayparts) + 1, count($dayparts));
$flag = false;
foreach ($dayparts as $daypart) {
if ($daypart->dayPartId == $daypart1->dayPartId) {
$flag = true;
}
}
$this->assertTrue($flag, 'Daypart ID ' . $daypart1->dayPartId . ' was not found after deleting a different daypart');
$daypart1->delete();
}
}

View File

@@ -0,0 +1,138 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboDisplayGroup;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Tests copying a display group.
*/
class DisplayGroupCopyTest extends LocalWebTestCase
{
use DisplayHelperTrait;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboDisplayGroup */
protected $displayGroup;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for Cache ' . get_class($this) . ' Test');
// Create a couple of displays to use in the test
$this->display = $this->createDisplay();
$this->display2 = $this->createDisplay();
// Create a display group and assign both displays
$this->displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create(
'phpunit_' . bin2hex(random_bytes(4)),
'',
0,
null
);
// Assign our two displays
$this->displayGroup->assignDisplay($this->display->displayId);
$this->displayGroup->assignDisplay($this->display2->displayId);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Display
$this->deleteDisplay($this->display);
$this->deleteDisplay($this->display2);
$this->displayGroup->delete();
}
// </editor-fold>
public function testCopyPlain()
{
$response = $this->sendRequest('POST', '/displaygroup/' . $this->displayGroup->displayGroupId . '/copy', [
'displayGroup' => 'phpunit_' . bin2hex(random_bytes(4)),
'description' => 'copied',
'copyMembers' => 0,
'copyAssignments' => 0,
'copyTags' => 0,
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame('copied', $object->data->description);
// Check there aren't any displays assigned.
$results = $this->getStore()->select('SELECT COUNT(*) AS cnt FROM lkdisplaydg WHERE displayGroupId = :displayGroupId', [
'displayGroupId' => $object->id
]);
$this->assertEquals(0, intval($results[0]['cnt']));
(new XiboDisplayGroup($this->getEntityProvider()))->getById($object->id)->delete();
}
public function testCopyMembers()
{
$response = $this->sendRequest('POST', '/displaygroup/' . $this->displayGroup->displayGroupId . '/copy', [
'displayGroup' => 'phpunit_' . bin2hex(random_bytes(4)),
'description' => 'copied',
'copyMembers' => 1,
'copyAssignments' => 0,
'copyTags' => 0,
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame('copied', $object->data->description);
// Check there aren't any displays assigned.
$results = $this->getStore()->select('SELECT COUNT(*) AS cnt FROM lkdisplaydg WHERE displayGroupId = :displayGroupId', [
'displayGroupId' => $object->id
]);
$this->assertEquals(2, intval($results[0]['cnt']));
(new XiboDisplayGroup($this->getEntityProvider()))->getById($object->id)->delete();
}
}

View File

@@ -0,0 +1,899 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCommand;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboDisplayGroup;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DisplayGroupTest
* @package Xibo\Tests\Integration
*/
class DisplayGroupTest extends LocalWebTestCase
{
use LayoutHelperTrait;
protected $startDisplayGroups;
protected $startDisplays;
protected $startLayouts;
protected $startCommands;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startDisplayGroups = (new XiboDisplayGroup($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->startDisplays = (new XiboDisplay($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->startLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->startCommands = (new XiboCommand($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all display groups that weren't there initially
$finalDisplayGroups = (new XiboDisplayGroup($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining display groups and nuke them
foreach ($finalDisplayGroups as $displayGroup) {
/** @var XiboDisplayGroup $displayGroup */
$flag = true;
foreach ($this->startDisplayGroups as $startGroup) {
if ($startGroup->displayGroupId == $displayGroup->displayGroupId) {
$flag = false;
}
}
if ($flag) {
try {
$displayGroup->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $displayGroup->displayGroupId . '. E:' . $e->getMessage());
}
}
}
// Tear down any displays that weren't there before
$finalDisplays = (new XiboDisplay($this->getEntityProvider()))->get();
# Loop over any remaining displays and nuke them
foreach ($finalDisplays as $display) {
/** @var XiboDisplay $display */
$flag = true;
foreach ($this->startDisplays as $startDisplay) {
if ($startDisplay->displayId == $display->displayId) {
$flag = false;
}
}
if ($flag) {
try {
$display->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $display->displayId . '. E:' . $e->getMessage());
}
}
}
// tearDown all layouts that weren't there initially
$finalLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining layouts and nuke them
foreach ($finalLayouts as $layout) {
/** @var XiboLayout $layout */
$flag = true;
foreach ($this->startLayouts as $startLayout) {
if ($startLayout->layoutId == $layout->layoutId) {
$flag = false;
}
}
if ($flag) {
try {
$layout->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $layout->layoutId . '. E:' . $e->getMessage());
}
}
}
// tearDown all commands that weren't there initially
$finalCommands = (new XiboCommand($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining commands and nuke them
foreach ($finalCommands as $command) {
/** @var XiboCommand $command */
$flag = true;
foreach ($this->startCommands as $startCom) {
if ($startCom->commandId == $command->commandId) {
$flag = false;
}
}
if ($flag) {
try {
$command->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $command->commandId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* List all display groups known empty
* @group minimal
* @group destructive
*/
public function testListEmpty()
{
if (count($this->startDisplayGroups) > 0) {
$this->skipTest("There are pre-existing DisplayGroups");
return;
}
$response = $this->sendRequest('GET','/displaygroup');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# There should be no DisplayGroups in the system
$this->assertEquals(0, $object->data->recordsTotal);
}
/**
* testAddSuccess - test adding various Display Groups that should be valid
* @dataProvider provideSuccessCases
* @group minimal
*/
public function testAddSuccess($groupName, $groupDescription, $isDynamic, $expectedDynamic, $dynamicCriteria, $expectedDynamicCriteria)
{
// Loop through any pre-existing DisplayGroups to make sure we're not
// going to get a clash
foreach ($this->startDisplayGroups as $tmpGroup) {
if ($tmpGroup->displayGroup == $groupName) {
$this->skipTest("There is a pre-existing DisplayGroup with this name");
return;
}
}
$response = $this->sendRequest('POST','/displaygroup', [
'displayGroup' => $groupName,
'description' => $groupDescription,
'isDynamic' => $isDynamic,
'dynamicCriteria' => $dynamicCriteria
]);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($groupName, $object->data->displayGroup);
$this->assertSame($groupDescription, $object->data->description);
$this->assertSame($expectedDynamic, $object->data->isDynamic);
$this->assertSame($expectedDynamicCriteria, $object->data->dynamicCriteria);
# Check that the group was really added
$displayGroups = (new XiboDisplayGroup($this->getEntityProvider()))->get(['length' => 1000]);
$this->assertEquals(count($this->startDisplayGroups) + 1, count($displayGroups));
# Check that the group was added correctly
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->getById($object->id);
$this->assertSame($groupName, $displayGroup->displayGroup);
$this->assertSame($groupDescription, $displayGroup->description);
$this->assertSame($expectedDynamic, $displayGroup->isDynamic);
$this->assertSame($expectedDynamicCriteria, $displayGroup->dynamicCriteria);
# Clean up the DisplayGroup as we no longer need it
$this->assertTrue($displayGroup->delete(), 'Unable to delete ' . $displayGroup->displayGroupId);
}
/**
* testAddFailure - test adding various Display Groups that should be invalid
* @dataProvider provideFailureCases
* @group minimal
*/
public function testAddFailure($groupName, $groupDescription, $isDynamic, $dynamicCriteria)
{
$response = $this->sendRequest('POST','/displaygroup', [
'displayGroup' => $groupName,
'description' => $groupDescription,
'isDynamic' => $isDynamic,
'dynamicCriteria' => $dynamicCriteria
]);
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* List all display groups known set
* @group minimal
* @depends testAddSuccess
*/
public function testListKnown()
{
# Load in a known set of display groups
# We can assume this works since we depend upon the test which
# has previously added and removed these without issue:
$cases = $this->provideSuccessCases();
$displayGroups = [];
// Check each possible case to ensure it's not pre-existing
// If it is, skip over it
foreach ($cases as $case) {
$flag = true;
foreach ($this->startDisplayGroups as $tmpGroup) {
if ($case[0] == $tmpGroup->displayGroup) {
$flag = false;
}
}
if ($flag) {
$displayGroups[] = (new XiboDisplayGroup($this->getEntityProvider()))->create($case[0],$case[1],$case[2],$case[3]);
}
}
$response = $this->sendRequest('GET','/displaygroup');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# There should be as many groups as we created plus the number we started with in the system
$this->assertEquals(count($displayGroups) + count($this->startDisplayGroups), $object->data->recordsTotal);
# Clean up the groups we created
foreach ($displayGroups as $group) {
$group->delete();
}
}
/**
* List specific display groups
* @group minimal
* @group destructive
* @depends testListKnown
* @depends testAddSuccess
* @dataProvider provideSuccessCases
*/
public function testListFilter($groupName, $groupDescription, $isDynamic, $expectedDynamic, $dynamicCriteria, $expectedDynamicCriteria)
{
if (count($this->startDisplayGroups) > 0) {
$this->skipTest("There are pre-existing DisplayGroups");
return;
}
# Load in a known set of display groups
# We can assume this works since we depend upon the test which
# has previously added and removed these without issue:
$cases = $this->provideSuccessCases();
$displayGroups = [];
foreach ($cases as $case) {
$displayGroups[] = (new XiboDisplayGroup($this->getEntityProvider()))->create($case[0], $case[1], $case[2], $case[3]);
}
$response = $this->sendRequest('GET','/displaygroup', [
'displayGroup' => $groupName
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# There should be at least one match
$this->assertGreaterThanOrEqual(1, $object->data->recordsTotal);
$flag = false;
# Check that for the records returned, $groupName is in the groups names
foreach ($object->data->data as $group) {
if (strpos($groupName, $group->displayGroup) == 0) {
$flag = true;
}
else {
// The object we got wasn't the exact one we searched for
// Make sure all the words we searched for are in the result
foreach (array_map('trim',explode(",",$groupName)) as $word) {
assertTrue((strpos($word, $group->displayGroup) !== false), 'Group returned did not match the query string: ' . $group->displayGroup);
}
}
}
$this->assertTrue($flag, 'Search term not found');
foreach ($displayGroups as $group) {
$group->delete();
}
}
/**
* Each array is a test run
* Format
* (Group Name, Group Description, isDynamic, Returned isDynamic (0 or 1),
* Criteria for Dynamic group, Returned Criteria for Dynamic group)
* For example, if you set isDynamic to 0 and send criteria, it will come back
* with criteria = null
* These are reused in other tests so please ensure Group Name is unique
* through the dataset
* @return array
*/
public function provideSuccessCases()
{
return [
// Multi-language non-dynamic groups
'English 1' => ['phpunit test group', 'Api', 0, 0, null, null],
'English 2' => ['another phpunit test group', 'Api', 0, 0, null, null],
'French 1' => ['Test de Français 1', 'Bienvenue à la suite de tests Xibo', 0, 0, null, null],
'German 1' => ['Deutsch Prüfung 1', 'Weiß mit schwarzem Text', 0, 0, null, null],
'Simplified Chinese 1' => ['试验组', '测试组描述', 0, 0, null, null],
// Multi-language dynamic groups
'English Dynamic 1' => ['phpunit test dynamic group', 'Api', 1, 1, 'test', 'test'],
'French Dynamic 1' => ['Test de Français 2', 'Bienvenue à la suite de tests Xibo', 1, 1, 'test', 'test'],
'German Dynamic 1' => ['Deutsch Prüfung 2', 'Weiß mit schwarzem Text', 1, 1, 'test', 'test'],
// Tests for the various allowed values for isDynamic = 1
'isDynamic on' => ['phpunit group dynamic is on', 'Api', 'on', 1, 'test', 'test'],
'isDynamic true' => ['phpunit group dynamic is true', 'Api', 'true', 1, 'test', 'test'],
// Invalid isDynamic flag (the CMS sanitises these for us to false)
'isDynamic is 7 null criteria' => ['Invalid isDynamic flag 1', 'Invalid isDynamic flag', 7, 0, null, null],
'isDynamic is 7 with criteria' => ['Invalid isDynamic flag 2 ', 'Invalid isDynamic flag', 7, 0, 'criteria', 'criteria'],
'isDynamic is invalid null criteria' => ['Invalid isDynamic flag alpha 1', 'Invalid isDynamic flag alpha', 'invalid', 0, null, null],
'isDynamic is invalid with criteria' => ['Invalid isDynamic flag alpha 2', 'Invalid isDynamic flag alpha', 'invalid', 0, 'criteria', 'criteria']
];
}
/**
* Each array is a test run
* Format
* (Group Name, Group Description, isDynamic, Criteria for Dynamic group)
* @return array
*/
public function provideFailureCases()
{
return [
// Description is limited to 255 characters
'Description over 254 characters' => ['Too long description', Random::generateString(255), 0, null],
// If isDynamic = 1 then criteria must be set
'No dynamicCriteria on dynamic group' => ['No dynamic criteria', 'No dynamic criteria', 1, null],
// Missing group names
'Group name empty' => ['', 'Group name is empty', 0, null],
'Group name null' => [null, 'Group name is null', 0, null]
];
}
/**
* Try and add two display groups with the same name
* @group minimal
* @depends testAddSuccess
*/
public function testAddDuplicate()
{
$flag = true;
foreach ($this->startDisplayGroups as $group) {
if ($group->displayGroup == 'phpunit displaygroup') {
$flag = false;
}
}
# Load in a known display group if it's not there already
if ($flag) {
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit displaygroup', 'phpunit displaygroup', 0, '');
}
$response = $this->sendRequest('POST','/displaygroup', [
'displayGroup' => 'phpunit displaygroup',
'description' => 'phpunit displaygroup',
'isDynamic' => 0,
'dynamicCriteria' => ''
]);
$this->assertSame(409, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
$displayGroup->delete();
}
/**
* Edit an existing display group
* @depends testAddSuccess
* @group minimal
*/
public function testEdit()
{
foreach ($this->startDisplayGroups as $group) {
if ($group->displayGroup == 'phpunit displaygroup') {
$this->skipTest('displayGroup already exists with that name');
return;
}
}
# Load in a known display group
/** @var XiboDisplayGroup $displayGroup */
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit displaygroup', 'phpunit displaygroup', 0, '');
# Change the group name and description
# Change it to a dynamic group with a fixed criteria
$name = Random::generateString(8, 'phpunit');
$description = Random::generateString(8, 'description');
$criteria = 'test';
$response = $this->sendRequest('PUT','/displaygroup/' . $displayGroup->displayGroupId, [
'displayGroup' => $name,
'description' => $description,
'isDynamic' => 1,
'dynamicCriteria' => $criteria
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
# Examine the returned object and check that it's what we expect
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->displayGroup);
$this->assertSame($description, $object->data->description);
$this->assertSame(1, $object->data->isDynamic);
$this->assertSame($criteria, $object->data->dynamicCriteria);
# Check that the group was actually renamed
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $displayGroup->displayGroup);
$this->assertSame($description, $displayGroup->description);
$this->assertSame(1, $displayGroup->isDynamic);
$this->assertSame($criteria, $displayGroup->dynamicCriteria);
# Clean up the DisplayGroup as we no longer need it
$displayGroup->delete();
}
/**
* Test delete
* @depends testAddSuccess
* @group minimal
*/
public function testDelete()
{
$name1 = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
# Load in a couple of known display groups
$displayGroup1 = (new XiboDisplayGroup($this->getEntityProvider()))->create($name1, 'phpunit description', 0, '');
$displayGroup2 = (new XiboDisplayGroup($this->getEntityProvider()))->create($name2, 'phpunit description', 0, '');
# Delete the one we created last
$response = $this->sendRequest('DELETE','/displaygroup/' . $displayGroup2->displayGroupId);
# This should return 204 for success
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Check only one remains
$groups = (new XiboDisplayGroup($this->getEntityProvider()))->get();
$this->assertEquals(count($this->startDisplayGroups) + 1, count($groups));
$flag = false;
foreach ($groups as $group) {
if ($group->displayGroupId == $displayGroup1->displayGroupId) {
$flag = true;
}
}
$this->assertTrue($flag, 'DisplayGroup ID ' . $displayGroup1->displayGroupId . ' was not found after deleting a different DisplayGroup');
# Clean up
$displayGroup1->delete();
}
/**
* Assign new displays Test
*/
public function testAssignDisplay()
{
# Create a Display in the system
$hardwareId = Random::generateString(12, 'phpunit');
$this->getXmdsWrapper()->RegisterDisplay($hardwareId, 'PHPUnit Test Display');
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get();
$display = null;
foreach ($displays as $disp) {
if ($disp->license == $hardwareId) {
$display = $disp;
}
}
if ($display === null) {
$this->fail('Display was not added correctly');
}
# Create a DisplayGroup to add the display to
$name = Random::generateString(8, 'phpunit');
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Call assign display to display group
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/display/assign', [
'displayId' => [$display->displayId]
]);
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Get a list of all Displays in the group
$displays = (new XiboDisplay($this->getEntityProvider()))->get(['displayGroupId' => $displayGroup->displayGroupId]);
# Check that there's only us in that group
$this->assertEquals(1, count($displays));
$this->assertEquals($display->displayId, $displays[0]->displayId);
# Clean up
$displayGroup->delete();
$display->delete();
}
/**
* Try to assign display to isDisplaySpecific displayGroupId
*/
public function testAssignDisplayFailure()
{
# Create a Display in the system
$hardwareId = Random::generateString(12, 'phpunit');
$this->getXmdsWrapper()->RegisterDisplay($hardwareId, 'PHPUnit Test Display');
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get();
$display = null;
foreach ($displays as $disp) {
if ($disp->license == $hardwareId) {
$display = $disp;
}
}
if ($display === null) {
$this->fail('Display was not added correctly');
}
# Call assign display to display specific display group
$response = $this->sendRequest('POST','/displaygroup/' . $display->displayGroupId . '/display/assign', [
'displayId' => [$display->displayId]
]);
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* Unassign displays Test
*/
public function testUnassignDisplay()
{
# Create a Display in the system
$hardwareId = Random::generateString(12, 'phpunit');
$this->getXmdsWrapper()->RegisterDisplay($hardwareId, 'PHPUnit Test Display');
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get();
$display = null;
foreach ($displays as $disp) {
if ($disp->license == $hardwareId) {
$display = $disp;
}
}
if ($display === null) {
$this->fail('Display was not added correctly');
}
# Create display group
$name = Random::generateString(8, 'phpunit');
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Assign display to display group
$displayGroup->assignDisplay([$display->displayId]);
# Unassign display from display group
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/display/unassign', [
'displayId' => [$display->displayId]
]);
$this->assertSame(200, $response->getStatusCode());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Clean up
$displayGroup->delete();
$display->delete();
}
/**
* Try to unassign display from isDisplaySpecific displayGroupId
*/
public function testUnassignDisplayFailure()
{
# Create a Display in the system
$hardwareId = Random::generateString(12, 'phpunit');
$this->getXmdsWrapper()->RegisterDisplay($hardwareId, 'PHPUnit Test Display');
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get();
$display = null;
foreach ($displays as $disp) {
if ($disp->license == $hardwareId) {
$display = $disp;
}
}
if ($display === null) {
$this->fail('Display was not added correctly');
}
# Create display group
$name = Random::generateString(8, 'phpunit');
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Assign display to display group - should be successful
$displayGroup->assignDisplay([$display->displayId]);
# Unassign display from isDisplaySpecific display group - should fail
$response = $this->sendRequest('POST','/displaygroup/' . $display->displayGroupId . '/display/unassign', [
'displayId' => [$display->displayId]
]);
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* Assign new display group Test
*/
public function testAssignGroup()
{
# Generate new random names
$name = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
$displayGroup2 = (new XiboDisplayGroup($this->getEntityProvider()))->create($name2, 'phpunit description', 0, '');
# Assign second display group to the first one
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/displayGroup/assign', [
'displayGroupId' => [$displayGroup2->displayGroupId]
]);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
$displayGroup2->delete();
}
/**
* Unassign displays group Test
*/
public function testUnassignGroup()
{
# Generate new random names
$name = Random::generateString(8, 'PARENT');
$name2 = Random::generateString(8, 'CHILD');
# Create new display groups
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
$displayGroup2 = (new XiboDisplayGroup($this->getEntityProvider()))->create($name2, 'phpunit description', 0, '');
# Assign second display group to the first one
$displayGroup->assignDisplayGroup([$displayGroup2->displayGroupId]);
# Unassign second display group from the first one
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/displayGroup/unassign', [
'displayGroupId' => [$displayGroup2->displayGroupId]
]);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
$displayGroup2->delete();
}
/**
* Assign new media file to a group Test
*/
public function testAssignMedia()
{
# Generate new random name
$name = Random::generateString(8, 'phpunit');
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Upload a known files
$media = (new XiboLibrary($this->getEntityProvider()))->create('API video 12', PROJECT_ROOT . '/tests/resources/HLH264.mp4');
$media2 = (new XiboLibrary($this->getEntityProvider()))->create('API image 12', PROJECT_ROOT . '/tests/resources/xts-night-001.jpg');
# Assign two files o the display group and unassign one of them
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/media/assign', [
'mediaId' => [$media->mediaId, $media2->mediaId],
'unassignMediaId' => [$media2->mediaId]
]);
$this->assertSame(200, $response->getStatusCode());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
$media->delete();
$media2->delete();
}
/**
* Unassign media files from a group Test
*/
public function testUnassignMedia()
{
# Generate new random name
$name = Random::generateString(8, 'phpunit');
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Upload a known file
$media = (new XiboLibrary($this->getEntityProvider()))->create('API image 29', PROJECT_ROOT . '/tests/resources/xts-night-001.jpg');
# Assign media to display Group
$displayGroup->assignMedia([$media->mediaId]);
# Unassign the media from the display group
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/media/unassign', [
'mediaId' => [$media->mediaId]
]);
$this->assertSame(200, $response->getStatusCode());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
$media->delete();
}
/**
* Assign new layouts to a group Test
*/
public function testAssignLayout()
{
# Create new random name
$name = Random::generateString(8, 'phpunit');
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Create new layouts
$layout = $this->createLayout();
$layout2 = $this->createLayout();
# Assign both layouts to display group then unassign the second layout from it
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/layout/assign', [
'layoutId' => [$layout->layoutId, $layout2->layoutId],
'unassignLayoutsId' => [$layout2->layoutId]
]);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
$layout->delete();
$layout2->delete();
}
/**
* Unassign layouts from a group Test
*/
public function testUnassignLayout()
{
# Create new random name
$name = Random::generateString(8, 'phpunit');
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Create new layout
$layout = $this->createLayout();
# assign layout to display group
$displayGroup->assignLayout([$layout->layoutId]);
# unassign layout from display group
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/layout/unassign', [
'layoutId' => [$layout->layoutId]
]);
$this->assertSame(200, $response->getStatusCode());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
$layout->delete();
}
/**
* Collect now action test
*/
public function testCollectNow()
{
# Generate random name
$name = Random::generateString(8, 'phpunit');
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Call callectNow
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/action/collectNow');
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
}
/**
* Change Layout action test
*/
public function testChangeLayout()
{
# Generate random name
$name = Random::generateString(8, 'phpunit');
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Create new layout
$layout = $this->createLayout();
# Call changeLayout
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/action/changeLayout', [
'layoutId' => $layout->layoutId,
'duration' => 900,
'downloadRequired' => 1,
'changeMode' => 'queue'
]);
# Check if successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
$layout->delete();
}
/**
* Revert to Schedule action test
*/
public function testRevertToSchedule()
{
# Generate random name and create new display group
$name = Random::generateString(8, 'phpunit');
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Call RevertToSchedule
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/action/revertToSchedule');
# Check if successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
}
/**
* Send command action test
*/
public function testCommand()
{
# Generate random name and create new display group
$name = Random::generateString(8, 'phpunit');
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create($name, 'phpunit description', 0, '');
# Create new command
$command = (new XiboCommand($this->getEntityProvider()))->create('phpunit command', 'phpunit description', 'phpunitcode');
# Send command to display group
$response = $this->sendRequest('POST','/displaygroup/' . $displayGroup->displayGroupId . '/action/command' , [
'commandId' => $command->commandId
]);
# Check if successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Clean up
$displayGroup->delete();
$command->delete();
}
}

View File

@@ -0,0 +1,171 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\OAuth2\Client\Entity\XiboDisplayProfile;
/**
* Class DisplayProfileTest
* @package Xibo\Tests\Integration
*/
class DisplayProfileTest extends \Xibo\Tests\LocalWebTestCase
{
protected $startProfiles;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startProfiles = (new XiboDisplayProfile($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all profiles that weren't there initially
$finalProfiles = (new XiboDisplayProfile($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
// Loop over any remaining profiles and nuke them
foreach ($finalProfiles as $displayProfile) {
/** @var XiboDisplayProfile $displayProfile */
$flag = true;
foreach ($this->startProfiles as $startProfile) {
if ($startProfile->displayProfileId == $displayProfile->displayProfileId) {
$flag = false;
}
}
if ($flag) {
try {
$displayProfile->delete();
} catch (\Exception $e) {
$this->getLogger()->error('Unable to delete ' . $displayProfile->displayProfileId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* Shows all display profiles
*/
public function testListAll()
{
# Get list of all display profiles
$response = $this->sendRequest('GET','/displayprofile');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
/**
* testAddSuccess - test adding various display profiles that should be valid
* @dataProvider provideSuccessCases
* @group minimal
*/
public function testAddSuccess($profileName, $profileType, $profileIsDefault)
{
// Loop through any pre-existing profiles to make sure we're not
// going to get a clash
foreach ($this->startProfiles as $tmpProfile) {
if ($tmpProfile->name == $profileName) {
$this->skipTest("There is a pre-existing profiles with this name");
return;
}
}
$response = $this->sendRequest('POST','/displayprofile', [
'name' => $profileName,
'type' => $profileType,
'isDefault' => $profileIsDefault
]);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($profileName, $object->data->name);
$this->assertSame($profileType, $object->data->type);
$this->assertSame($profileIsDefault, $object->data->isDefault);
# Check that the profile was added correctly
$profile = (new XiboDisplayProfile($this->getEntityProvider()))->getById($object->id);
$this->assertSame($profileName, $profile->name);
$this->assertSame($profileType, $profile->type);
# Clean up the Profiles as we no longer need it
$this->assertTrue($profile->delete(), 'Unable to delete ' . $profile->displayProfileId);
}
/**
* Each array is a test run
* Format (profile name, type(windows/android), isDefault flag)
* @return array
*/
public function provideSuccessCases()
{
// Cases we provide to testAddSuccess, you can extend it by simply adding new case here
return [
'Android notDefault' => ['test profile', 'android', 0],
'Windows notDefault' => ['different test profile', 'windows', 0],
'French Android' => ['Test de Français 1', 'android', 0],
'Linux' => ['Test de Français 1', 'linux', 0],
'Tizen' => ['Test de Français 1', 'sssp', 0],
'webOS' => ['Test de Français 1', 'lg', 0]
];
}
/**
* testAddFailure - test adding various profiles that should be invalid
* @dataProvider provideFailureCases
*/
public function testAddFailure($profileName, $profileType, $profileIsDefault)
{
# Add new display profile with arguments from provideFailureCases
$response = $this->sendRequest('POST','/displayprofile', [
'name' => $profileName,
'type' => $profileType,
'isDefault' => $profileIsDefault
]);
# Check if it fails as expected
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* Each array is a test run
* Format (profile name, type(windows/android), isDefault flag)
* @return array
*/
public function provideFailureCases()
{
# Cases we provide to testAddFailure, you can extend it by simply adding new case here
return [
'NULL Type' => ['no type', NULL, 0],
'NULL name' => [NULL, 'android', 1],
'is Default 1' => ['TEST PHP', 'android', 1]
];
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* Copyright (C) 2019 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplayProfile;
use Xibo\OAuth2\Client\Exception\XiboApiException;
/**
* Class DisplayProfileTest
* @package Xibo\Tests\Integration
*/
class DisplayProfileTestDelete extends \Xibo\Tests\LocalWebTestCase
{
/** @var XiboDisplayProfile */
private $displayProfile;
public function setup()
{
parent::setup();
$this->displayProfile = (new XiboDisplayProfile($this->getEntityProvider()))->create(Random::generateString(), 'android', 0);
}
protected function tearDown()
{
if ($this->displayProfile !== null) {
$this->displayProfile->delete();
}
parent::tearDown();
}
/**
* Test delete
*/
public function testDelete()
{
// Delete the one we created last
$response = $this->sendRequest('DELETE','/displayprofile/' . $this->displayProfile->displayProfileId);
// This should return 204 for success
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
// Check only one remains
try {
$displayProfile = (new XiboDisplayProfile($this->getEntityProvider()))->getById($this->displayProfile->displayProfileId);
$this->fail('Display profile ID ' . $this->displayProfile->displayProfileId . ' was not found after deleting a different Display Profile');
} catch (XiboApiException $exception) {
// We know we've deleted it, so no clear for tearDown
$this->displayProfile = null;
}
}
}

View File

@@ -0,0 +1,112 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplayProfile;
/**
* Class DisplayProfileTest
* @package Xibo\Tests\Integration
*/
class DisplayProfileTestEdit extends \Xibo\Tests\LocalWebTestCase
{
/** @var XiboDisplayProfile */
private $displayProfile;
public function setup()
{
parent::setup();
$this->displayProfile = (new XiboDisplayProfile($this->getEntityProvider()))->create(Random::generateString(), 'android', 0);
}
protected function tearDown()
{
$this->displayProfile->delete();
parent::tearDown();
}
/**
* Edit an existing profile
*/
public function testEdit()
{
// Call edit on the profile.
$name = Random::generateString(8, 'phpunit');
$response = $this->sendRequest('PUT','/displayprofile/' . $this->displayProfile->displayProfileId, [
'name' => $name,
'type' => $this->displayProfile->type,
'isDefault' => $this->displayProfile->isDefault
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
// Examine the returned object and check that it's what we expect
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame('android', $object->data->type);
// Check that the profile was actually renamed
$displayProfile = (new XiboDisplayProfile($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $displayProfile->name);
}
/**
* Edit an existing profile
*/
public function testEditConfig()
{
// Call edit on the profile.
$name = Random::generateString(8, 'phpunit');
$response = $this->sendRequest('PUT','/displayprofile/' . $this->displayProfile->displayProfileId, [
'name' => $name,
'type' => $this->displayProfile->type,
'isDefault' => $this->displayProfile->isDefault,
'emailAddress' => 'phpunit@xibo.org.uk'
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getStatusCode());
$object = json_decode($response->getBody());
// Examine the returned object and check that it's what we expect
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame('android', $object->data->type);
foreach ($object->data->config as $config) {
if ($config->name === 'emailAddress') {
$this->assertSame('phpunit@xibo.org.uk', $config->value, json_encode($object->data->config));
}
}
// Check that the profile was actually renamed
$displayProfile = (new XiboDisplayProfile($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $displayProfile->name);
}
}

View File

@@ -0,0 +1,293 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
class DisplayTest extends \Xibo\Tests\LocalWebTestCase
{
protected $startDisplays;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startDisplays = (new XiboDisplay($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Tear down any displays that weren't there before
$finalDisplays = (new XiboDisplay($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining displays and nuke them
foreach ($finalDisplays as $display) {
/** @var XiboDisplay $display */
$flag = true;
foreach ($this->startDisplays as $startDisplay) {
if ($startDisplay->displayId == $display->displayId) {
$flag = false;
}
}
if ($flag) {
try {
$display->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $display->displayId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* Shows list of all displays Test
*/
public function testListAll()
{
# Get all displays
$response = $this->sendRequest('GET','/display');
# Check if successful
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
/**
* Delete Display Test
*/
public function testDelete()
{
# Create a Display in the system
$hardwareId = Random::generateString(12, 'phpunit');
$this->getXmdsWrapper()->RegisterDisplay($hardwareId, 'PHPUnit Test Display');
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get(['hardwareKey' => $hardwareId]);
if (count($displays) != 1)
$this->fail('Display was not added correctly');
/** @var XiboDisplay $display */
$display = $displays[0];
$response = $this->sendRequest('DELETE','/display/' . $display->displayId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
}
/**
* Edit Display test, expecting success
*/
public function testEditSuccess()
{
# Create a Display in the system
$hardwareId = Random::generateString(12, 'phpunit');
$this->getXmdsWrapper()->RegisterDisplay($hardwareId, 'PHPUnit Test Display');
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get(['hardwareKey' => $hardwareId]);
if (count($displays) != 1) {
$this->fail('Display was not added correctly');
}
/** @var XiboDisplay $display */
$display = $displays[0];
$auditingTime = time()+3600;
# Edit display and change its name
$response = $this->sendRequest('PUT','/display/' . $display->displayId, [
'display' => 'API EDITED',
'defaultLayoutId' => $display->defaultLayoutId,
'auditingUntil' => Carbon::createFromTimestamp($auditingTime)->format(DateFormatHelper::getSystemFormat()),
'licensed' => $display->licensed,
'license' => $display->license,
'incSchedule' => $display->incSchedule,
'emailAlert' => $display->emailAlert,
'wakeOnLanEnabled' => $display->wakeOnLanEnabled,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
# Check if display has new edited name
$this->assertSame('API EDITED', $object->data->display);
}
/**
* Edit Display Type and reference, expecting success
*/
public function testEditDisplayType()
{
# Create a Display in the system
$hardwareId = Random::generateString(12, 'phpunit');
$this->getXmdsWrapper()->RegisterDisplay($hardwareId, 'PHPUnit Test Display Type');
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get(['hardwareKey' => $hardwareId]);
if (count($displays) != 1) {
$this->fail('Display was not added correctly');
}
/** @var XiboDisplay $display */
$display = $displays[0];
$auditingTime = time()+3600;
# Edit display and change its name
$response = $this->sendRequest('PUT', '/display/' . $display->displayId, [
'display' => 'PHPUnit Test Display Type - EDITED',
'defaultLayoutId' => $display->defaultLayoutId,
'auditingUntil' => Carbon::createFromTimestamp($auditingTime)->format(DateFormatHelper::getSystemFormat()),
'licensed' => $display->licensed,
'license' => $display->license,
'incSchedule' => $display->incSchedule,
'emailAlert' => $display->emailAlert,
'wakeOnLanEnabled' => $display->wakeOnLanEnabled,
'displayTypeId' => 1,
'ref1' => 'Lorem ipsum',
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
# Check if display has new edited name
$this->assertSame(1, $object->data->displayTypeId);
$this->assertSame('Lorem ipsum', $object->data->ref1);
}
/**
* Edit Display test, expecting failure
*/
public function testEditFailure()
{
# Create a Display in the system
$hardwareId = Random::generateString(12, 'phpunit');
$this->getXmdsWrapper()->RegisterDisplay($hardwareId, 'PHPUnit Test Display');
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get(['hardwareKey' => $hardwareId]);
if (count($displays) != 1)
$this->fail('Display was not added correctly');
/** @var XiboDisplay $display */
$display = $displays[0];
# Edit display and change its hardwareKey
$response = $this->sendRequest('PUT','/display/' . $display->displayId, [
'display' => 'API EDITED',
'defaultLayoutId' => $display->defaultLayoutId,
'licensed' => $display->licensed,
'license' => null,
'incSchedule' => $display->incSchedule,
'emailAlert' => $display->emailAlert,
'wakeOnLanEnabled' => $display->wakeOnLanEnabled,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if call failed as expected (license cannot be null)
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getBody());
}
/**
* Request screenshot Test
*/
public function testScreenshot()
{
# Generate names for display and xmr channel
$hardwareId = Random::generateString(12, 'phpunit');
$xmrChannel = Random::generateString(50);
# This is a dummy pubKey and isn't used by anything important
$xmrPubkey = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDmdnXL4gGg3yJfmqVkU1xsGSQI
3b6YaeAKtWuuknIF1XAHAHtl3vNhQN+SmqcNPOydhK38OOfrdb09gX7OxyDh4+JZ
inxW8YFkqU0zTqWaD+WcOM68wTQ9FCOEqIrbwWxLQzdjSS1euizKy+2GcFXRKoGM
pbBhRgkIdydXoZZdjQIDAQAB
-----END PUBLIC KEY-----';
# Register our display
$this->getXmdsWrapper()->RegisterDisplay($hardwareId,
'PHPUnit Test Display',
'windows',
null,
null,
null,
'00:16:D9:C9:AL:69',
$xmrChannel,
$xmrPubkey
);
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get(['hardwareKey' => $hardwareId]);
if (count($displays) != 1)
$this->fail('Display was not added correctly');
/** @var XiboDisplay $display */
$display = $displays[0];
# Check if xmr channel and pubkey were registered correctly
$this->assertSame($xmrChannel, $display->xmrChannel, 'XMR Channel not set correctly by XMDS Register Display');
$this->assertSame($xmrPubkey, $display->xmrPubKey, 'XMR PubKey not set correctly by XMDS Register Display');
# Call request screenshot
$response = $this->sendRequest('PUT','/display/requestscreenshot/' . $display->displayId);
# Check if successful
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
}
/**
* Wake On Lan Test
*/
public function testWoL()
{
# Create dummy hardware key and mac address
$hardwareId = Random::generateString(12, 'phpunit');
$macAddress = '00-16-D9-C9-AE-69';
# Register our display
$this->getXmdsWrapper()->RegisterDisplay($hardwareId,
'PHPUnit Test Display',
'windows',
null,
null,
null,
$macAddress,
Random::generateString(50),
Random::generateString(50)
);
# Now find the Id of that Display
$displays = (new XiboDisplay($this->getEntityProvider()))->get(['hardwareKey' => $hardwareId]);
if (count($displays) != 1)
$this->fail('Display was not added correctly');
/** @var XiboDisplay $display */
$display = $displays[0];
# Check if mac address was added correctly
$this->assertSame($macAddress, $display->macAddress, 'Mac Address not set correctly by XMDS Register Display');
$auditingTime = time()+3600;
# Edit display and add broadcast channel
$display->edit(
$display->display,
$display->description,
$display->tags,
Carbon::createFromTimestamp($auditingTime)->format(DateFormatHelper::getSystemFormat()),
$display->defaultLayoutId,
$display->licensed,
$display->license,
$display->incSchedule,
$display->emailAlert,
$display->alertTimeout,
$display->wakeOnLanEnabled,
null,
'127.0.0.1');
# Call WOL
$response = $this->sendRequest('POST','/display/wol/' . $display->displayId);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
/**
* Class FaultTest
* @package Xibo\Tests\Integration
*/
class FaultTest extends \Xibo\Tests\LocalWebTestCase
{
/**
* Collect data
* This test modifies headers and we therefore need to run in a separate process
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testCollect()
{
$response = $this->sendRequest('GET','/fault/collect', ['outputLog' => 'on']);
$this->assertSame(200, $response->getStatusCode());
}
/**
* test turning debug on
*/
public function testDebugOn()
{
$response = $this->sendRequest('PUT','/fault/debug/on');
$this->assertSame(200, $response->getStatusCode());
}
/**
* test turning debug off
*/
public function testDebugOff()
{
$response = $this->sendRequest('PUT','/fault/debug/off');
$this->assertSame(200, $response->getStatusCode());
}
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* Copyright (C) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Entity\Display;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\LocalWebTestCase;
class FontTest extends LocalWebTestCase
{
use DisplayHelperTrait;
private $testFileName = 'PHPUNIT FONT TEST';
private $testFilePath = PROJECT_ROOT . '/tests/resources/UglyTypist.ttf';
protected $startFonts;
// TODO create api wrapper for fonts :)
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startFonts = $this->getEntityProvider()->get('/fonts', ['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all media files that weren't there initially
$finalFonts = $this->getEntityProvider()->get('/fonts', ['start' => 0, 'length' => 10000]);
# Loop over any remaining font files and nuke them
foreach ($finalFonts as $font) {
$flag = true;
foreach ($this->startFonts as $startFont) {
if ($startFont['id'] == $font['id']) {
$flag = false;
}
}
if ($flag) {
try {
$this->getEntityProvider()->delete('/fonts/'.$font['id'].'/delete');
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete font ' . $font['id'] . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* List all file in library
*/
public function testListAll()
{
# Get all library items
$response = $this->sendRequest('GET', '/fonts');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
// we expect fonts distributed with CMS to be there.
$this->assertNotEmpty($object->data->data);
}
public function testUpload()
{
$uploadResponse = $this->uploadFontFile();
$uploadedFileObject = $uploadResponse['files'][0];
$this->assertNotEmpty($uploadedFileObject);
$this->assertSame(filesize($this->testFilePath), $uploadedFileObject['size']);
$this->assertSame(basename($this->testFilePath), $uploadedFileObject['fileName']);
$this->getLogger()->debug(
'Uploaded font ' . $uploadedFileObject['name'] .
' with ID ' . $uploadedFileObject['id'] .
' Stored as ' . $uploadedFileObject['fileName']
);
$fontRecord = $this->getEntityProvider()->get('/fonts', ['name' => $this->testFileName])[0];
$this->assertNotEmpty($fontRecord);
$this->assertSame(filesize($this->testFilePath), $fontRecord['size']);
$this->assertSame(basename($this->testFilePath), $fontRecord['fileName']);
}
public function testDelete()
{
$upload = $this->uploadFontFile();
$response = $this->sendRequest('DELETE', '/fonts/' . $upload['files'][0]['id']. '/delete');
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
}
public function testFontDependencies()
{
$upload = $this->uploadFontFile();
$size = $upload['files'][0]['size'];
$md5 = $upload['files'][0]['md5'];
// Create a Display
$display = $this->createDisplay();
$this->displaySetStatus($display, Display::$STATUS_DONE);
$this->displaySetLicensed($display);
// Call Required Files
$rf = $this->getXmdsWrapper()->RequiredFiles($display->license);
$this->assertContains('file download="http" size="'.$size.'" md5="'.$md5.'" saveAs="'.basename($this->testFilePath).'" type="dependency" fileType="font" ', $rf, 'Font not in Required Files');
// Delete the Display
$this->deleteDisplay($display);
}
public function testFontCss()
{
$fontCssPath = PROJECT_ROOT . '/library/fonts/fonts.css';
// upload file, this should also update fonts.css file
$this->uploadFontFile();
// read css file
$fontsCss = file_get_contents($fontCssPath);
// get the record
$fontRecord = $this->getEntityProvider()->get('/fonts', ['name' => $this->testFileName])[0];
// check if the uploaded font was added to player fonts.css file.
$this->assertContains('font-family: \''.$fontRecord['familyName'].'\';', $fontsCss, 'Font not in fonts.css');
$this->assertContains('src: url(\''.basename($this->testFilePath).'\');', $fontsCss, 'Font not in fonts.css');
}
private function uploadFontFile()
{
$payload = [
[
'name' => 'name',
'contents' => $this->testFileName
],
[
'name' => 'files',
'contents' => fopen($this->testFilePath, 'r')
]
];
return $this->getEntityProvider()->post('/fonts', ['multipart' => $payload]);
}
}

View File

@@ -0,0 +1,434 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\Tests\Helper\LayoutHelperTrait;
/**
* Class AboutTest
* @package Xibo\Tests\Integration
*/
class InteractiveFeaturesTest extends \Xibo\Tests\LocalWebTestCase
{
use LayoutHelperTrait;
/** @var XiboLayout */
protected $layout;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this));
// Create a Layout
$this->layout = $this->createLayout();
// Get Draft
$layout = $this->getDraft($this->layout);
$this->addSimpleTextWidget($layout);
$this->layout = $this->publish($this->layout);
// Set the Layout status
$this->setLayoutStatus($this->layout, 1);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
parent::tearDown();
}
// </editor-fold>
/**
* Test Add Region Drawer
*/
public function testAddDrawer()
{
$layout = $this->checkout($this->layout);
// add Drawer Region
$response = $this->sendRequest('POST', '/region/drawer/' . $layout->layoutId);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$body = json_decode($response->getBody());
$this->assertSame(201, $body->status);
$this->assertSame(true, $body->success);
$this->assertSame(false, $body->grid);
$this->assertNotEmpty($body->data, 'Empty Data');
$this->assertSame(1, $body->data->isDrawer);
$this->assertContains('drawer', $body->data->name);
// get the layout
$layout = $this->getEntityProvider()->get('/layout', ['layoutId' => $layout->layoutId, 'embed' => 'regions'])[0];
// check if regions and drawers arrays are not empty
$this->assertNotEmpty($layout['drawers']);
$this->assertNotEmpty($layout['regions']);
}
/**
* Test Add Region Drawer
*/
public function testDeleteDrawer()
{
$layout = $this->checkout($this->layout);
// add Drawer Region
$drawer = $this->getEntityProvider()->post('/region/drawer/' . $layout->layoutId);
// delete Drawer Region
$response = $this->sendRequest('DELETE', '/region/' . $drawer['regionId']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$body = json_decode($response->getBody());
$this->assertSame(204, $body->status);
$this->assertSame(true, $body->success);
}
public function testListAll()
{
$response = $this->sendRequest('GET','/action');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
/**
* Add Action
* @dataProvider AddActionSuccessCases
* @param string $source
* @param string $triggerType
* @param string|null $triggerCode
* @param string $actionType
* @param string $target
* @param string|null $layoutCode
*/
public function testAddActionSuccess(?string $source, ?string $triggerType, ?string $triggerCode, string $actionType, string $target, ?string $layoutCode)
{
$layout = $this->checkout($this->layout);
$sourceId = null;
$targetId = null;
$widgetId = null;
// Add a couple of text widgets to the region
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget B',
'duration' => 100,
'useDuration' => 1
]);
$widget2 = (new XiboText($this->getEntityProvider()))->hydrate($response);
// depending on the source from AddActionsCases, the sourceId will be different
if ($source === 'layout') {
$sourceId = $layout->layoutId;
} elseif ($source === 'region') {
$sourceId = $layout->regions[0]->regionId;
} else {
$sourceId = $widget->widgetId;
}
// depending on the target screen|region we may need targetId
if ($target === 'region') {
$targetId = $layout->regions[0]->regionId;
}
if ($actionType == 'navWidget') {
$widgetId = $widget2->widgetId;
}
$response = $this->sendRequest('POST', '/action', [
'triggerType' => $triggerType,
'triggerCode' => $triggerCode,
'actionType' => $actionType,
'target' => $target,
'targetId' => $targetId,
'widgetId' => $widgetId,
'layoutCode' => $layoutCode,
'source' => $source,
'sourceId' => $sourceId,
'layoutId' => $layout->layoutId
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$body = json_decode($response->getBody());
$this->assertSame(201, $body->status);
$this->assertSame(true, $body->success);
$this->assertSame(false, $body->grid);
$this->assertNotEmpty($body->data, 'Empty Data');
$this->assertSame($layout->layoutId, $body->data->layoutId);
$this->assertSame($sourceId, $body->data->sourceId);
$this->assertSame($triggerType, $body->data->triggerType);
$this->assertSame($triggerCode, $body->data->triggerCode);
$this->assertSame($actionType, $body->data->actionType);
$this->assertSame($target, $body->data->target);
$this->assertSame($targetId, $body->data->targetId);
}
/**
* Each array is a test run
* Format (string $source, string $triggerType, string|null $triggerCode, string $actionType, string $target, string LayoutCode)
* @return array
*/
public function AddActionSuccessCases()
{
return [
'Layout' => ['layout', 'touch', 'trigger code', 'next', 'screen', null],
'Layout with region target' => ['layout', 'touch', null, 'previous', 'region', null],
'Region' => ['region', 'webhook', 'test', 'previous', 'screen', null],
'Region with region target' => ['region', 'touch', null, 'previous', 'region', null],
'Widget' => ['widget', 'touch', null, 'next', 'screen', null],
'Widget with region target' => ['widget', 'touch', null, 'next', 'region', null],
'Navigate to Widget' => ['layout', 'touch', null, 'navWidget', 'screen', null],
'Navigate to Layout with code' => ['layout', 'touch', null, 'navLayout', 'screen', 'CodeIdentifier'],
'Web UI' => [null, null, null, 'next', 'screen', null]
];
}
public function testEditAction()
{
$layout = $this->checkout($this->layout);
$action = $this->getEntityProvider()->post('/action', [
'actionType' => 'previous',
'target' => 'screen',
'layoutId' => $layout->layoutId
]);
$response = $this->sendRequest('PUT', '/action/' . $action['actionId'], [
'source' => 'layout',
'sourceId' => $layout->layoutId,
'triggerType' => 'webhook',
'triggerCode' => 'new code',
'actionType' => 'next',
'target' => 'region',
'targetId' => $layout->regions[0]->regionId
], ['Content-Type' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$body = json_decode($response->getBody());
$this->assertSame(200, $body->status);
$this->assertSame(true, $body->success);
$this->assertSame(false, $body->grid);
$this->assertNotEmpty($body->data, 'Empty Data');
$this->assertSame($layout->layoutId, $body->data->sourceId);
$this->assertSame($layout->layoutId, $body->data->layoutId);
$this->assertSame('webhook', $body->data->triggerType);
$this->assertSame('new code', $body->data->triggerCode);
$this->assertSame('next', $body->data->actionType);
$this->assertSame('region', $body->data->target);
$this->assertSame($layout->regions[0]->regionId, $body->data->targetId);
}
public function testDeleteAction()
{
$layout = $this->checkout($this->layout);
$action = $this->getEntityProvider()->post('/action', [
'triggerType' => 'webhook',
'triggerCode' => 'test',
'actionType' => 'previous',
'target' => 'screen',
'layoutId' => $layout->layoutId
]);
$response = $this->sendRequest('DELETE', '/action/' . $action['actionId']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$body = json_decode($response->getBody());
$this->assertSame(204, $body->status);
$this->assertSame(true, $body->success);
$this->assertSame(false, $body->grid);
// check if one action remains with our Layout Id.
$actions = $this->getEntityProvider()->get('/action', ['sourceId' => $layout->layoutId]);
$this->assertSame(0, count($actions));
}
/**
* Add Action
* @dataProvider editActionFailureCases
* @param string $source
* @param string $triggerType
* @param string|null $triggerCode
* @param string $actionType
* @param string $target
*/
public function testEditActionFailure(string $source, string $triggerType, ?string $triggerCode, string $actionType, string $target)
{
$layout = $this->checkout($this->layout);
$action = $this->getEntityProvider()->post('/action', [
'actionType' => 'previous',
'target' => 'screen',
'layoutId' => $layout->layoutId
]);
$targetId = null;
$widgetId = null;
$layoutCode = null;
if ($source === 'layout') {
$sourceId = $layout->layoutId;
} elseif ($source === 'region') {
$sourceId = $layout->regions[0]->regionId;
} else {
$sourceId = null;
}
$response = $this->sendRequest('PUT', '/action/' . $action['actionId'], [
'triggerType' => $triggerType,
'triggerCode' => $triggerCode,
'actionType' => $actionType,
'target' => $target,
'targetId' => $targetId,
'source' => $source,
'sourceId' => $sourceId
]);
$body = json_decode($response->getBody());
// in other failure cases, we expect to get invalidArgument exception.
$this->assertSame(422, $response->getStatusCode());
// get the error message for cases and make sure we return correct one.
if ($source === 'playlist') {
$this->assertSame('Invalid source', $body->error);
}
// wrong trigger type case
if ($triggerType === 'notExistingType') {
$this->assertSame('Invalid trigger type', $body->error);
}
// wrong trigger type case
if ($actionType === 'wrongAction') {
$this->assertSame('Invalid action type', $body->error);
}
// wrong target case
if ($target === 'world') {
$this->assertSame('Invalid target', $body->error);
}
// test case when we have target set to region, but we don't set targetId to any regionId
if ($target === 'region') {
$this->assertSame('Please select a Region', $body->error);
}
// trigger code in non layout
if ($triggerType === 'webhook' && $triggerCode === null) {
$this->assertSame('Please provide trigger code', $body->error);
}
// navWidget without widgetId
if ($actionType === 'navWidget' && $widgetId == null) {
$this->assertSame('Please select a Widget', $body->error);
}
// navLayout without layoutCode
if ($actionType === 'navLayout' && $layoutCode == null) {
$this->assertSame('Please enter Layout code', $body->error);
}
}
/**
* Each array is a test run
* Format (string $source, string $triggerType, string|null $triggerCode, string $actionType, string $target)
* @return array
*/
public function editActionFailureCases()
{
return [
'Wrong source' => ['playlist', 'touch', null, 'next', 'screen'],
'Wrong trigger type' => ['layout', 'notExistingType', null, 'previous', 'screen'],
'Wrong action type' => ['layout', 'touch', null, 'wrongAction', 'screen'],
'Wrong target' => ['layout', 'touch', null, 'next', 'world'],
'Target region without targetId' => ['layout', 'touch', 'trigger code', 'next', 'region'],
'Missing trigger code for webhook' => ['region', 'webhook', null, 'next', 'screen'],
'Navigate to Widget without widgetId' => ['layout', 'touch', null, 'navWidget', 'screen'],
'Navigate to Layout without layoutCode' => ['layout', 'touch', null, 'navLayout', 'screen']
];
}
public function testCopyLayoutWithActions()
{
$layout = $this->checkout($this->layout);
$this->getEntityProvider()->post('/action', [
'triggerType' => 'touch',
'actionType' => 'previous',
'target' => 'screen',
'layout' => $layout->layoutId
]);
$this->layout = $this->publish($this->layout);
$response = $this->sendRequest('POST', '/layout/copy/' . $this->layout->layoutId, ['copyMediaFiles' => 0, 'name' => Random::generateString()]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response);
$body = json_decode($response->getBody());
$this->assertSame(201, $body->status);
$newLayoutId = $body->id;
$newLayout = $this->getEntityProvider()->get('/layout', ['layoutId' => $newLayoutId, 'embed' => 'regions,actions'])[0];
$this->assertNotEmpty($newLayout['actions']);
// delete the copied layout
(new XiboLayout($this->getEntityProvider()))->getById($newLayoutId)->delete();
}
}

View File

@@ -0,0 +1,88 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutDraftTest
* @package Xibo\Tests\integration
*/
class LayoutDraftTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var XiboLayout */
private $layout;
public function setup()
{
parent::setup();
$this->layout = $this->createLayout();
}
public function tearDown()
{
parent::tearDown();
// This should always be the original, regardless of whether we checkout/discard/etc
$this->layout->delete();
}
/**
* Test adding a region to a Layout that has been checked out, but use the parent
*/
public function testAddRegionCheckoutParent()
{
// Add region to our layout with data from regionSuccessCases
$response = $this->sendRequest('POST','/region/' . $this->layout->layoutId, [
'width' => 100,
'height' => 100,
'top' => 10,
'left' => 10
]);
$this->assertSame(422, $response->getStatusCode(), 'Status Incorrect');
$object = json_decode($response->getBody());
$this->assertSame(false, $object->success);
$this->assertSame(422, $object->httpStatus);
}
/**
* Test adding a region to a Layout that has been checked out, using the draft
*/
public function testAddRegionCheckout()
{
// Checkout the Parent, but add a Region to the Original
$layout = $this->getDraft($this->layout);
// Add region to our layout with data from regionSuccessCases
$response = $this->sendRequest('POST','/region/' . $layout->layoutId, [
'width' => 100,
'height' => 100,
'top' => 10,
'left' => 10
]);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
}
}

View File

@@ -0,0 +1,120 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Exception\XiboApiException;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
class LayoutLockTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var XiboLayout */
private $layout;
public function setup()
{
parent::setup();
$this->layout = $this->createLayout();
// Get Draft
$layout = $this->getDraft($this->layout);
$this->addSimpleWidget($layout);
$this->layout = $this->publish($this->layout);
// Set the Layout status
$this->setLayoutStatus($this->layout, 1);
}
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
parent::tearDown();
}
public function testIsLockedObject()
{
// draft
$layout = $this->checkout($this->layout);
// add simple Widget via API
$this->addSimpleWidget($layout);
// Get the Layout object via web request
$response = $this->sendRequest('GET', '/layout', ['layoutId' => $layout->layoutId], [], 'layoutLock');
$body = json_decode($response->getBody());
$layoutObject = $body[0];
// check if the isLocked dynamic object is there and is not empty, then check the values inside of it.
// we expect it to be locked with our LayoutId and API entryPoint
$this->assertNotEmpty($layoutObject->isLocked);
$this->assertSame($layout->layoutId, $layoutObject->isLocked->layoutId);
$this->assertSame('API', $layoutObject->isLocked->entryPoint);
}
public function testApiToWeb()
{
// draft
$layout = $this->checkout($this->layout);
// add simple Widget via API
$this->addSimpleWidget($layout);
// layout should be locked for our User with API entry point for 5 min.
// attempt to add another Widget via web request
$response = $this->sendRequest('POST', '/playlist/widget/clock/' . $layout->regions[0]->regionPlaylist->playlistId, [
'duration' => 100,
'useDuration' => 1
], [], 'layoutLock');
$this->assertSame(403, $response->getStatusCode());
$body = json_decode($response->getBody());
$this->assertSame(403, $body->httpStatus);
$this->assertContains('Layout ID ' . $layout->layoutId . ' is locked by another User! Lock expires on:', $body->error);
}
public function testWebToApi()
{
// draft
$layout = $this->checkout($this->layout);
// call Layout status via web request, this will trigger the Layout Lock Middleware as well
$this->sendRequest('GET', '/layout/status/' . $layout->layoutId, [], [], 'layoutLock');
// attempt to add Widget via API
try {
$this->addSimpleWidget($layout);
} catch (XiboApiException $exception) {
$this->assertContains('Layout ID ' . $layout->layoutId . ' is locked by another User! Lock expires on:', $exception->getMessage());
}
}
}

View File

@@ -0,0 +1,903 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCampaign;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboRegion;
use Xibo\OAuth2\Client\Entity\XiboResolution;
use Xibo\Support\Exception\InvalidArgumentException;
use Xibo\Support\Exception\NotFoundException;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LayoutTest
* @package Xibo\Tests\Integration
*/
class LayoutTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var XiboLayout[] */
protected $startLayouts;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all layouts that weren't there initially
$finalLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
// Loop over any remaining layouts and nuke them
foreach ($finalLayouts as $layout) {
/** @var XiboLayout $layout */
$flag = true;
foreach ($this->startLayouts as $startLayout) {
if ($startLayout->layoutId == $layout->layoutId) {
$flag = false;
}
}
if ($flag) {
try {
$layout->delete();
} catch (\Exception $e) {
$this->getLogger()->error('Unable to delete ' . $layout->layoutId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* @param $type
* @return int
*/
private function getResolutionId($type)
{
if ($type === 'landscape') {
$width = 1920;
$height = 1080;
} else if ($type === 'portrait') {
$width = 1080;
$height = 1920;
} else {
return -10;
}
//$this->getLogger()->debug('Querying for ' . $width . ', ' . $height);
$resolutions = (new XiboResolution($this->getEntityProvider()))->get(['width' => $width, 'height' => $height]);
if (count($resolutions) <= 0)
return -10;
return $resolutions[0]->resolutionId;
}
/**
* List all layouts known empty
*/
public function testListEmpty()
{
# Check that there is one layout in the database (the 'default layout')
if (count($this->startLayouts) > 1) {
$this->skipTest("There are pre-existing Layouts");
return;
}
$response = $this->sendRequest('GET','/layout');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# There should be one default layout in the system
$this->assertEquals(1, $object->data->recordsTotal);
}
/**
* testAddSuccess - test adding various Layouts that should be valid
* @dataProvider provideSuccessCases
*/
public function testAddSuccess($layoutName, $layoutDescription, $layoutTemplateId, $layoutResolutionType)
{
$layoutResolutionId = $this->getResolutionId($layoutResolutionType);
# Create layouts with arguments from provideSuccessCases
$response = $this->sendRequest('POST','/layout', [
'name' => $layoutName,
'description' => $layoutDescription,
'layoutId' => $layoutTemplateId,
'resolutionId' => $layoutResolutionId
]);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($layoutName, $object->data->layout);
$this->assertSame($layoutDescription, $object->data->description);
# Check that the layout was really added
$layouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->assertEquals(count($this->startLayouts) + 1, count($layouts));
# Check that the layout was added correctly
$layout = (new XiboLayout($this->getEntityProvider()))->getById($object->id);
$this->assertSame($layoutName, $layout->layout);
$this->assertSame($layoutDescription, $layout->description);
# Clean up the Layout as we no longer need it
$this->assertTrue($layout->delete(), 'Unable to delete ' . $layout->layoutId);
}
/**
* testAddFailure - test adding various Layouts that should be invalid
* @dataProvider provideFailureCases
*/
public function testAddFailure($layoutName, $layoutDescription, $layoutTemplateId, $layoutResolutionType)
{
$layoutResolutionId = $this->getResolutionId($layoutResolutionType);
# Create layouts with arguments from provideFailureCases
$request = $this->createRequest('POST','/layout');
$request->withParsedBody([
'name' => $layoutName,
'description' => $layoutDescription,
'layoutId' => $layoutTemplateId,
'resolutionId' => $layoutResolutionId
]);
try {
$this->app->handle($request);
} catch (InvalidArgumentException $e) {
# check if they fail as expected
$this->assertSame(422, $e->getCode(), 'Expecting failure, received ' . $e->getMessage());
} catch (NotFoundException $e ) {
$this->assertSame(404, $e->getCode(), 'Expecting failure, received ' . $e->getMessage());
}
}
/**
* List all layouts known set
* @group minimal
*/
public function testListKnown()
{
$cases = $this->provideSuccessCases();
$layouts = [];
// Check each possible case to ensure it's not pre-existing
// If it is, skip over it
foreach ($cases as $case) {
$flag = true;
foreach ($this->startLayouts as $tmpLayout) {
if ($case[0] == $tmpLayout->layout) {
$flag = false;
}
}
if ($flag) {
$layouts[] = (new XiboLayout($this->getEntityProvider()))->create($case[0],$case[1],$case[2],$this->getResolutionId($case[3]));
}
}
$response = $this->sendRequest('GET','/layout');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# There should be as many layouts as we created plus the number we started with in the system
$this->assertEquals(count($layouts) + count($this->startLayouts), $object->data->recordsTotal);
# Clean up the Layouts we created
foreach ($layouts as $lay) {
$lay->delete();
}
}
/**
* List specific layouts
* @group minimal
* @group destructive
* @depends testListKnown
* @depends testAddSuccess
* @dataProvider provideSuccessCases
*/
public function testListFilter($layoutName, $layoutDescription, $layoutTemplateId, $layoutResolutionType)
{
if (count($this->startLayouts) > 1) {
$this->skipTest("There are pre-existing Layouts");
return;
}
# Load in a known set of layouts
# We can assume this works since we depend upon the test which
# has previously added and removed these without issue:
$cases = $this->provideSuccessCases();
$layouts = [];
foreach ($cases as $case) {
$layouts[] = (new XiboLayout($this->getEntityProvider()))->create($case[0], $case[1], $case[2], $this->getResolutionId($case[3]));
}
// Fitler for our specific layout
$response = $this->sendRequest('GET','/layout', ['name' => $layoutName]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# There should be at least one match
$this->assertGreaterThanOrEqual(1, $object->data->recordsTotal);
$flag = false;
# Check that for the records returned, $layoutName is in the groups names
foreach ($object->data->data as $lay) {
if (strpos($layoutName, $lay->layout) == 0) {
$flag = true;
}
else {
// The object we got wasn't the exact one we searched for
// Make sure all the words we searched for are in the result
foreach (array_map('trim',explode(",",$layoutName)) as $word) {
assertTrue((strpos($word, $lay->layout) !== false), 'Layout returned did not match the query string: ' . $lay->layout);
}
}
}
$this->assertTrue($flag, 'Search term not found');
// Remove the Layouts we've created
foreach ($layouts as $lay) {
$lay->delete();
}
}
/**
* Each array is a test run
* Format (LayoutName, description, layoutID (template), resolution ID)
* @return array
*/
public function provideSuccessCases()
{
# Data for testAddSuccess, easily expandable - just add another set of data below
return [
// Multi-language layouts
'English 1' => ['phpunit test Layout', 'Api', NULL, 'landscape'],
'French 1' => ['Test de Français 1', 'Bienvenue à la suite de tests Xibo', NULL, 'landscape'],
'German 1' => ['Deutsch Prüfung 1', 'Weiß mit schwarzem Text', NULL, 'landscape'],
'Simplified Chinese 1' => ['试验组', '测试组描述', NULL, 'landscape'],
'Portrait layout' => ['Portrait layout', '1080x1920', '', 'portrait'],
'No Description' => ['Just the title and resolution', NULL, '', 'portrait'],
'Just title' => ['Just the name', NULL, NULL, 'portrait']
];
}
/**
* Each array is a test run
* Format (LayoutName, description, layoutID (template), resolution ID)
* @return array
*/
public function provideFailureCases()
{
# Data for testAddfailure, easily expandable - just add another set of data below
return [
// Description is limited to 255 characters
'Description over 254 characters' => ['Too long description', Random::generateString(255), '', 'landscape'],
// Missing layout names
'layout name empty' => ['', 'Layout name is empty', '', 'landscape'],
'Layout name null' => [null, 'Layout name is null', '', 'landscape'],
'Wrong resolution ID' => ['id not found', 'not found exception', '', 'invalid']
];
}
/**
* Try and add two layouts with the same name
*/
public function testAddDuplicate()
{
# Check if there are layouts with that name already in the system
$flag = true;
foreach ($this->startLayouts as $layout) {
if ($layout->layout == 'phpunit layout') {
$flag = false;
}
}
# Load in a known layout if it's not there already
$landscapeId = $this->getResolutionId('landscape');
if ($flag) {
(new XiboLayout($this->getEntityProvider()))->create(
'phpunit layout',
'phpunit layout',
'',
$landscapeId
);
}
$response = $this->sendRequest('POST','/layout', [
'name' => 'phpunit layout',
'description' => 'phpunit layout',
'resolutionId' => $landscapeId
]);
$this->assertSame(409, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode() . '. Body = ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(false, $object->success);
$this->assertContains('You already own a Layout called ', $object->error);
}
/**
* Edit an existing layout
*/
public function testEdit()
{
// Create a known layout with a random name for us to work with.
// it will automatically get deleted in tearDown()
$layout = $this->createLayout();
// We do not need to checkout the Layout to perform an edit of its top level data.
// Change the layout name and description
$name = Random::generateString(8, 'phpunit');
$description = Random::generateString(8, 'description');
$response = $this->sendRequest('PUT','/layout/' . $layout->layoutId, [
'name' => $name,
'description' => $description
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
# Examine the returned object and check that it's what we expect
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->layout);
$this->assertSame($description, $object->data->description);
# Check that the layout was actually renamed
$layout = (new XiboLayout($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $layout->layout);
$this->assertSame($description, $layout->description);
# Clean up the Layout as we no longer need it
$layout->delete();
}
/**
* Edit an existing layout that should fail because of negative value in the backgroundzIndex
*/
public function testEditFailure()
{
// Create a known layout with a random name for us to work with.
// it will automatically get deleted in tearDown()
$layout = $this->createLayout();
// Set a background z-index that is outside parameters
$response = $this->sendRequest('PUT','/layout/' . $layout->layoutId, [
'backgroundColor' => $layout->backgroundColor,
'backgroundzIndex' => -1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getBody());
}
/**
* Test delete
* @group minimal
*/
public function testDelete()
{
$name1 = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
# Load in a couple of known layouts
$layout1 = (new XiboLayout($this->getEntityProvider()))->create($name1, 'phpunit description', '', $this->getResolutionId('landscape'));
$layout2 = (new XiboLayout($this->getEntityProvider()))->create($name2, 'phpunit description', '', $this->getResolutionId('landscape'));
# Delete the one we created last
$response = $this->sendRequest('DELETE','/layout/' . $layout2->layoutId);
# This should return 204 for success
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Check only one remains
$layouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->assertEquals(count($this->startLayouts) + 1, count($layouts));
$flag = false;
foreach ($layouts as $layout) {
if ($layout->layoutId == $layout1->layoutId) {
$flag = true;
}
}
$this->assertTrue($flag, 'Layout ID ' . $layout1->layoutId . ' was not found after deleting a different layout');
$layout1->delete();
}
/**
* Try to delete a layout that is assigned to a campaign
*/
public function testDeleteAssigned()
{
# Load in a known layout
/** @var XiboLayout $layout */
$layout = (new XiboLayout($this->getEntityProvider()))->create('phpunit layout assigned', 'phpunit layout', '', $this->getResolutionId('landscape'));
// Make a campaign with a known name
$name = Random::generateString(8, 'phpunit');
$campaign = (new XiboCampaign($this->getEntityProvider()))->create($name);
// Assign layout to campaign
$this->getEntityProvider()->post('/campaign/layout/assign/' . $campaign->campaignId, [
'layoutId' => $layout->layoutId
]);
# Check if it's assigned
$campaignCheck = (new XiboCampaign($this->getEntityProvider()))->getById($campaign->campaignId);
$this->assertSame(1, $campaignCheck->numberLayouts);
# Try to Delete the layout assigned to the campaign
$response = $this->sendRequest('DELETE','/layout/' . $layout->layoutId);
# This should return 204 for success
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
}
/**
* Test Layout Retire
*/
public function testRetire()
{
// Get known layout
$layout = $this->createLayout();
// Call retire
$response = $this->sendRequest('PUT','/layout/retire/' . $layout->layoutId, [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
// Get the same layout again and make sure its retired = 1
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layout->layoutId);
$this->assertSame(1, $layout->retired, 'Retired flag not updated');
}
/**
* Test Unretire
*/
public function testUnretire()
{
// Get known layout
/** @var XiboLayout $layout */
$layout = $this->createLayout();
// Retire it
$this->getEntityProvider()->put('/layout/retire/' . $layout->layoutId);
// Call layout edit with this Layout
$response = $this->sendRequest('PUT','/layout/unretire/' . $layout->layoutId, [], [
'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
]);
// Make sure that was successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
// Get the same layout again and make sure its retired = 0
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layout->layoutId);
$this->assertSame(0, $layout->retired, 'Retired flag not updated. ' . $response->getBody());
}
/**
* Add new region to a specific layout
* @dataProvider regionSuccessCases
*/
public function testAddRegionSuccess($regionWidth, $regionHeight, $regionTop, $regionLeft)
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
// Add region to our layout with data from regionSuccessCases
$response = $this->sendRequest('POST','/region/' . $layout->layoutId, [
'width' => $regionWidth,
'height' => $regionHeight,
'top' => $regionTop,
'left' => $regionLeft
]);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if region has intended values
$this->assertSame($regionWidth, $object->data->width);
$this->assertSame($regionHeight, $object->data->height);
$this->assertSame($regionTop, $object->data->top);
$this->assertSame($regionLeft, $object->data->left);
}
/**
* Each array is a test run
* Format (width, height, top, left)
* @return array
*/
public function regionSuccessCases()
{
return [
// various correct regions
'region 1' => [500, 350, 100, 150],
'region 2' => [350, 200, 50, 50],
'region 3' => [69, 69, 20, 420],
'region 4 no offsets' => [69, 69, 0, 0]
];
}
/**
* testAddFailure - test adding various regions that should be invalid
* @dataProvider regionFailureCases
*/
public function testAddRegionFailure($regionWidth, $regionHeight, $regionTop, $regionLeft, $expectedHttpCode, $expectedWidth, $expectedHeight)
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
# Add region to our layout with datafrom regionFailureCases
$response = $this->sendRequest('POST','/region/' . $layout->layoutId, [
'width' => $regionWidth,
'height' => $regionHeight,
'top' => $regionTop,
'left' => $regionLeft
]);
# Check if we receive failure as expected
$this->assertSame($expectedHttpCode, $response->getStatusCode(), 'Expecting failure, received ' . $response->getBody());
if ($expectedHttpCode == 200) {
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($expectedWidth, $object->data->width);
$this->assertSame($expectedHeight, $object->data->height);
}
}
/**
* Each array is a test run
* Format (width, height, top, left)
* @return array
*/
public function regionFailureCases()
{
return [
// various incorrect regions
'region no size' => [NULL, NULL, 20, 420, 200, 250, 250],
'region negative dimensions' => [-69, -420, 20, 420, 422, null, null]
];
}
/**
* Edit known region
*/
public function testEditRegion()
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
# Add region to our layout
$region = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,75,125);
# Edit region
$response = $this->sendRequest('PUT','/region/' . $region->regionId, [
'name' => $layout->layout . ' edited',
'width' => 700,
'height' => 500,
'top' => 400,
'left' => 400,
'loop' => 0,
'zIndex' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if region has updated values
$this->assertSame(700, $object->data->width);
$this->assertSame(500, $object->data->height);
$this->assertSame(400, $object->data->top);
$this->assertSame(400, $object->data->left);
}
/**
* Edit known region that should fail because of negative z-index value
*/
public function testEditRegionFailure()
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
# Add region to our layout
$region = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,75,125);
# Edit region
$response = $this->sendRequest('PUT','/region/' . $region->regionId, [
'width' => 700,
'height' => 500,
'top' => 400,
'left' => 400,
'loop' => 0,
'zIndex' => -1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if it failed
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getBody());
}
/**
* delete region test
*/
public function testDeleteRegion()
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
$region = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200, 670, 100, 100);
# Delete region
$response = $this->sendRequest('DELETE','/region/' . $region->regionId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
}
/**
* Add tag to a layout
*/
public function testAddTag()
{
# Create layout
$name = Random::generateString(8, 'phpunit');
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', $this->getResolutionId('landscape'));
# Assign new tag to our layout
$response = $this->sendRequest('POST','/layout/' . $layout->layoutId . '/tag' , [
'tag' => ['API']
]);
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layout->layoutId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
foreach ($layout->tags as $tag) {
$this->assertSame('API', $tag['tag']);
}
}
/**
* Delete tags from layout
*/
public function testDeleteTag()
{
$name = Random::generateString(8, 'phpunit');
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', $this->getResolutionId('landscape'));
$tag = 'API';
$layout->addTag($tag);
$layout = (new XiboLayout($this->getEntityProvider()))->getById($layout->layoutId);
$response = $this->sendRequest('POST','/layout/' . $layout->layoutId . '/untag', [
'tag' => [$tag]
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$layout->delete();
}
/**
* Calculate layout status
*/
public function testStatus()
{
# Create layout
$name = Random::generateString(8, 'phpunit');
$layout = (new XiboLayout($this->getEntityProvider()))->create($name, 'phpunit description', '', $this->getResolutionId('landscape'));
# Calculate layouts status
$response = $this->sendRequest('GET','/layout/status/' . $layout->layoutId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
}
/**
* Copy Layout Test
*/
public function testCopy()
{
# Load in a known layout
/** @var XiboLayout $layout */
$layout = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit layout',
'',
$this->getResolutionId('landscape')
);
// Generate new random name
$nameCopy = Random::generateString(8, 'phpunit');
// Call copy
$response = $this->sendRequest('POST','/layout/copy/' . $layout->layoutId, [
'name' => $nameCopy,
'description' => 'Copy',
'copyMediaFiles' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if copied layout has correct name
$this->assertSame($nameCopy, $object->data->layout);
# Clean up the Layout as we no longer need it
$this->assertTrue($layout->delete(), 'Unable to delete ' . $layout->layoutId);
}
/**
* Position Test
*/
public function testPosition()
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
# Create Two known regions and add them to that layout
$region1 = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,670,75,125);
$region2 = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,475,625);
# Reposition regions on that layout
$regionJson = json_encode([
[
'regionid' => $region1->regionId,
'width' => 700,
'height' => 500,
'top' => 400,
'left' => 400
],
[
'regionid' => $region2->regionId,
'width' => 100,
'height' => 100,
'top' => 40,
'left' => 40
]
]);
$response = $this->sendRequest('PUT','/region/position/all/' . $layout->layoutId, [
'regions' => $regionJson
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(true, $object->success);
$this->assertSame(200, $object->status);
}
/**
* Position Test with incorrect parameters (missing height and incorrect spelling)
*/
public function testPositionFailure()
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
# Create Two known regions and add them to that layout
$region1 = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,670,75,125);
$region2 = (new XiboRegion($this->getEntityProvider()))->create($layout->layoutId, 200,300,475,625);
# Reposition regions on that layout with incorrect/missing parameters
$regionJson = json_encode([
[
'regionid' => $region1->regionId,
'width' => 700,
'top' => 400,
'left' => 400
],
[
'regionid' => $region2->regionId,
'heigTH' => 100,
'top' => 40,
'left' => 40
]
]);
$response = $this->sendRequest('PUT','/region/position/all/' . $layout->layoutId, [
'regions' => $regionJson
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
# Check if it fails as expected
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(false, $object->success);
$this->assertSame(422, $object->httpStatus);
}
/**
* Add a Drawer to the Layout
*/
public function testAddDrawer()
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
// Add Drawer
$response = $this->sendRequest('POST', '/region/drawer/' . $layout->layoutId);
// Check if successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
// Check if drawer has the right values
$this->assertSame($layout->width, $object->data->width);
$this->assertSame($layout->height, $object->data->height);
$this->assertSame(0, $object->data->top);
$this->assertSame(0, $object->data->left);
}
/**
* Edit a Drawer to the Layout
*/
public function testSaveDrawer()
{
// Create a Layout and Checkout
$layout = $this->createLayout();
$layout = $this->getDraft($layout);
// Add Drawer
$drawer = $this->getEntityProvider()->post('/region/drawer/' . $layout->layoutId);
// Save drawer
$response = $this->sendRequest('PUT', '/region/drawer/' . $drawer['regionId'], [
'width' => 1280,
'height' => 720
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
// Check if successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
// Check if drawer has the right values
$this->assertSame(1280, $object->data->width);
$this->assertSame(720, $object->data->height);
$this->assertSame(0, $object->data->top);
$this->assertSame(0, $object->data->left);
}
}

View File

@@ -0,0 +1,280 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\Tests\LocalWebTestCase;
class LibraryTest extends LocalWebTestCase
{
protected $startMedias;
protected $mediaName;
protected $mediaType;
protected $mediaId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startMedias = (new XiboLibrary($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all media files that weren't there initially
$finalMedias = (new XiboLibrary($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining media files and nuke them
foreach ($finalMedias as $media) {
/** @var XiboLibrary $media */
$flag = true;
foreach ($this->startMedias as $startMedia) {
if ($startMedia->mediaId == $media->mediaId) {
$flag = false;
}
}
if ($flag) {
try {
$media->deleteAssigned();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $media->mediaId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* List all file in library
*/
public function testListAll()
{
# Get all library items
$response = $this->sendRequest('GET','/library');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
/**
* Add new file to library
*/
public function testAdd()
{
# Using XiboLibrary wrapper to upload new file to the CMS, need to provide (name, file location)
$media = (new XiboLibrary($this->getEntityProvider()))->create('API video test', PROJECT_ROOT . '/tests/resources/HLH264.mp4');
$media->delete();
}
/**
* Add new file to library and replace old one in all layouts
*/
public function testReplace()
{
# Using XiboLibrary wrapper to upload new file to the CMS, need to provide (name, file location)
$media = (new XiboLibrary($this->getEntityProvider()))->create('flowers', PROJECT_ROOT . '/tests/resources/xts-flowers-001.jpg');
# Replace the image and update it in all layouts (name, file location, old media id, replace in all layouts flag, delete old revision flag)
$media2 = (new XiboLibrary($this->getEntityProvider()))->create('API replace image', PROJECT_ROOT . '/tests/resources/xts-flowers-002.jpg', $media->mediaId, 1, 1);
}
/**
* try to add not allowed filetype
*/
public function testAddEmpty()
{
# Using XiboLibrary wrapper to upload new file to the CMS, need to provide (name, file location)
$this->expectException('\Xibo\OAuth2\Client\Exception\XiboApiException');
$media = (new XiboLibrary($this->getEntityProvider()))->create('API incorrect file 2', PROJECT_ROOT . '/tests/resources/empty.txt');
}
/**
* Add tags to media
*/
public function testAddTag()
{
# Using XiboLibrary wrapper to upload new file to the CMS, need to provide (name, file location)
$media = (new XiboLibrary($this->getEntityProvider()))->create('flowers 2', PROJECT_ROOT . '/tests/resources/xts-flowers-001.jpg');
$response = $this->sendRequest('POST','/library/' . $media->mediaId . '/tag', [
'tag' => ['API']
]);
$media = (new XiboLibrary($this->getEntityProvider()))->getById($media->mediaId);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
foreach ($media->tags as $tag) {
$this->assertSame('API', $tag['tag']);
}
$media->delete();
}
/**
* Delete tags from media
* @group broken
*/
public function testDeleteTag()
{
# Using XiboLibrary wrapper to upload new file to the CMS, need to provide (name, file location)
$media = (new XiboLibrary($this->getEntityProvider()))->create('flowers', PROJECT_ROOT . '/tests/resources/xts-flowers-001.jpg');
$media->AddTag('API');
$media = (new XiboLibrary($this->getEntityProvider()))->getById($media->mediaId);
$this->assertSame('API', $media->tags);
$response = $this->sendRequest('POST','/library/' . $media->mediaId . '/untag', [
'tag' => ['API']
]);
$media = (new XiboLibrary($this->getEntityProvider()))->getById($media->mediaId);
print_r($media->tags);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$media->delete();
}
/**
* Edit media file
*/
public function testEdit()
{
# Using XiboLibrary wrapper to upload new file to the CMS, need to provide (name, file location)
$media = (new XiboLibrary($this->getEntityProvider()))->create('API video 4', PROJECT_ROOT . '/tests/resources/HLH264.mp4');
# Generate new random name
$name = Random::generateString(8, 'phpunit');
# Edit media file, change the name
$response = $this->sendRequest('PUT','/library/' . $media->mediaId, [
'name' => $name,
'duration' => 50,
'retired' => $media->retired,
'tags' => $media->tags,
'updateInLayouts' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertSame($name, $object->data->name);
$media = (new XiboLibrary($this->getEntityProvider()))->getById($media->mediaId);
$this->assertSame($name, $media->name);
$media->delete();
}
/**
* Test delete added media
*/
public function testDelete()
{
# Using XiboLibrary wrapper to upload new file to the CMS, need to provide (name, file location)
$media = (new XiboLibrary($this->getEntityProvider()))->create('API video 4', PROJECT_ROOT . '/tests/resources/HLH264.mp4');
# Delete added media file
$response = $this->sendRequest('DELETE','/library/' . $media->mediaId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
}
/**
* Library tidy
*/
public function testTidy()
{
$response = $this->sendRequest('DELETE','/library/tidy');
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
}
public function testUploadFromUrl()
{
shell_exec('cp -r ' . PROJECT_ROOT . '/tests/resources/rss/image1.jpg ' . PROJECT_ROOT . '/web');
$response = $this->sendRequest('POST','/library/uploadUrl', [
'url' => 'http://localhost/image1.jpg'
]);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertNotEmpty($object->data, 'Empty Response');
$this->assertSame('image', $object->data->mediaType);
$this->assertSame(0, $object->data->expires);
$this->assertSame('image1', $object->data->name);
$this->assertNotEmpty($object->data->mediaId, 'Not successful, MediaId is empty');
$module = $this->getEntityProvider()->get('/module', ['name' => 'Image']);
$moduleDefaultDuration = $module[0]['defaultDuration'];
$this->assertSame($object->data->duration, $moduleDefaultDuration);
shell_exec('rm -r ' . PROJECT_ROOT . '/web/image1.jpg');
}
public function testUploadFromUrlWithType()
{
shell_exec('cp -r ' . PROJECT_ROOT . '/tests/resources/rss/image2.jpg ' . PROJECT_ROOT . '/web');
$response = $this->getEntityProvider()->post('/library/uploadUrl?envelope=1', [
'url' => 'http://localhost/image2.jpg',
'type' => 'image'
]);
$this->assertSame(201, $response['status'], json_encode($response));
$this->assertNotEmpty($response['data'], 'Empty Response');
$this->assertSame('image', $response['data']['mediaType']);
$this->assertSame(0, $response['data']['expires']);
$this->assertSame('image2', $response['data']['name']);
$this->assertNotEmpty($response['data']['mediaId'], 'Not successful, MediaId is empty');
$module = $this->getEntityProvider()->get('/module', ['name' => 'Image']);
$moduleDefaultDuration = $module[0]['defaultDuration'];
$this->assertSame($response['data']['duration'], $moduleDefaultDuration);
shell_exec('rm -r ' . PROJECT_ROOT . '/web/image2.jpg');
}
public function testUploadFromUrlWithTypeAndName()
{
shell_exec('cp -r ' . PROJECT_ROOT . '/tests/resources/HLH264.mp4 ' . PROJECT_ROOT . '/web');
$response = $this->getEntityProvider()->post('/library/uploadUrl?envelope=1', [
'url' => 'http://localhost/HLH264.mp4',
'type' => 'video',
'optionalName' => 'PHPUNIT URL upload video'
]);
$this->assertSame(201, $response['status'], json_encode($response));
$this->assertNotEmpty($response['data'], 'Empty Response');
$this->assertSame('video', $response['data']['mediaType']);
$this->assertSame(0, $response['data']['expires']);
$this->assertSame('PHPUNIT URL upload video', $response['data']['name']);
$this->assertNotEmpty($response['data']['mediaId'], 'Not successful, MediaId is empty');
// for videos we expect the Media duration to be the actual video duration.
$this->assertSame($response['data']['duration'], 78);
shell_exec('rm -r ' . PROJECT_ROOT . '/web/HLH264.mp4');
}
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\Tests\LocalWebTestCase;
class MenuBoardCategoryTest extends LocalWebTestCase
{
private $menuBoard;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->menuBoard = $this->getEntityProvider()->post('/menuboard', [
'name' => Random::generateString(10, 'phpunit'),
'description' => 'Description for test Menu Board'
]);
}
public function tearDown()
{
parent::tearDown();
if ($this->menuBoard['menuId'] !== null) {
$this->getEntityProvider()->delete('/menuboard/' . $this->menuBoard['menuId']);
}
}
public function testListEmpty()
{
$response = $this->sendRequest('GET', '/menuboard/' . $this->menuBoard['menuId'] . '/categories');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertEquals(0, $object->data->recordsTotal);
}
public function testAdd()
{
$media = (new XiboLibrary($this->getEntityProvider()))->create(Random::generateString(10, 'API Image'), PROJECT_ROOT . '/tests/resources/xts-night-001.jpg');
$name = Random::generateString(10, 'Category Add');
$response = $this->sendRequest('POST', '/menuboard/' . $this->menuBoard['menuId'] . '/category', [
'name' => $name,
'mediaId' => $media->mediaId
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame($media->mediaId, $object->data->mediaId);
$media->delete();
}
public function testEdit()
{
$menuBoardCategory = $this->getEntityProvider()->post('/menuboard/' . $this->menuBoard['menuId'] . '/category', [
'name' => 'Test Menu Board Category Edit'
]);
$name = Random::generateString(10, 'Category Edit');
$response = $this->sendRequest('PUT', '/menuboard/' . $menuBoardCategory['menuCategoryId'] . '/category', [
'name' => $name,
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
}
public function testDelete()
{
$menuBoardCategory = $this->getEntityProvider()->post('/menuboard/' . $this->menuBoard['menuId'] . '/category', [
'name' => 'Test Menu Board Category Delete'
]);
$response = $this->sendRequest('DELETE', '/menuboard/' . $menuBoardCategory['menuCategoryId'] . '/category');
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
}
}

View File

@@ -0,0 +1,198 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\Tests\LocalWebTestCase;
class MenuBoardProductTest extends LocalWebTestCase
{
private $menuBoard;
private $menuBoardCategory;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->menuBoard = $this->getEntityProvider()->post('/menuboard', [
'name' => Random::generateString(10, 'phpunit'),
'description' => 'Description for test Menu Board'
]);
$this->menuBoardCategory = $this->getEntityProvider()->post('/menuboard/' . $this->menuBoard['menuId'] . '/category', [
'name' => 'Test Menu Board Category Edit'
]);
}
public function tearDown()
{
parent::tearDown();
if ($this->menuBoard['menuId'] !== null) {
$this->getEntityProvider()->delete('/menuboard/' . $this->menuBoard['menuId']);
}
}
public function testListEmpty()
{
$response = $this->sendRequest('GET', '/menuboard/' . $this->menuBoardCategory['menuCategoryId'] . '/products');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertEquals(0, $object->data->recordsTotal);
}
public function testAdd()
{
$media = (new XiboLibrary($this->getEntityProvider()))->create(Random::generateString(10, 'API Image'), PROJECT_ROOT . '/tests/resources/xts-night-001.jpg');
$name = Random::generateString(10, 'Product Add');
$response = $this->sendRequest('POST', '/menuboard/' . $this->menuBoardCategory['menuCategoryId'] . '/product', [
'name' => $name,
'mediaId' => $media->mediaId,
'price' => '$12.40',
'description' => 'Product Description',
'allergyInfo' => 'N/A',
'availability' => 1,
'productOptions' => ['small', 'medium', 'large'],
'productValues' => ['$10.40', '$15.40', '$20.20']
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame($media->mediaId, $object->data->mediaId);
$this->assertSame('$12.40', $object->data->price);
$this->assertSame('Product Description', $object->data->description);
$this->assertSame('N/A', $object->data->allergyInfo);
$this->assertSame(1, $object->data->availability);
// product options are ordered by option name
$this->assertSame($object->id, $object->data->productOptions[2]->menuProductId);
$this->assertSame('small', $object->data->productOptions[2]->option);
$this->assertSame('$10.40', $object->data->productOptions[2]->value);
$this->assertSame($object->id, $object->data->productOptions[1]->menuProductId);
$this->assertSame('medium', $object->data->productOptions[1]->option);
$this->assertSame('$15.40', $object->data->productOptions[1]->value);
$this->assertSame($object->id, $object->data->productOptions[0]->menuProductId);
$this->assertSame('large', $object->data->productOptions[0]->option);
$this->assertSame('$20.20', $object->data->productOptions[0]->value);
$media->delete();
}
/**
* @dataProvider provideFailureCases
*/
public function testAddFailure($name, $price)
{
$response = $this->sendRequest('POST', '/menuboard/' . $this->menuBoardCategory['menuCategoryId'] . '/product', [
'name' => $name,
'price' => $price
]);
# check if they fail as expected
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* Each array is a test run
* Format (name, price, productOptions, productValues)
* @return array
*/
public function provideFailureCases()
{
return [
'empty name' => ['', '$11', [], []],
'empty price' => ['Test Product', null, [], []]
];
}
public function testEdit()
{
$menuBoardProduct = $this->getEntityProvider()->post('/menuboard/' . $this->menuBoardCategory['menuCategoryId'] . '/product', [
'name' => 'Test Menu Board Product Edit',
'price' => '$11.11',
'productOptions' => ['small', 'medium', 'large'],
'productValues' => ['$10.40', '$15.40', '$20.20']
]);
$name = Random::generateString(10, 'Product Edit');
$response = $this->sendRequest('PUT', '/menuboard/' . $menuBoardProduct['menuProductId'] . '/product', [
'name' => $name,
'price' => '$9.99',
'description' => 'Product Description Edited',
'allergyInfo' => '',
'availability' => 1,
'productOptions' => ['small', 'medium', 'large'],
'productValues' => ['$8.40', '$12.40', '$15.20']
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame('$9.99', $object->data->price);
$this->assertSame('Product Description Edited', $object->data->description);
$this->assertSame('', $object->data->allergyInfo);
$this->assertSame(1, $object->data->availability);
// product options are ordered by option name
$this->assertSame($object->id, $object->data->productOptions[2]->menuProductId);
$this->assertSame('small', $object->data->productOptions[2]->option);
$this->assertSame('$8.40', $object->data->productOptions[2]->value);
$this->assertSame($object->id, $object->data->productOptions[1]->menuProductId);
$this->assertSame('medium', $object->data->productOptions[1]->option);
$this->assertSame('$12.40', $object->data->productOptions[1]->value);
$this->assertSame($object->id, $object->data->productOptions[0]->menuProductId);
$this->assertSame('large', $object->data->productOptions[0]->option);
$this->assertSame('$15.20', $object->data->productOptions[0]->value);
}
public function testDelete()
{
$menuBoardProduct = $this->getEntityProvider()->post('/menuboard/' . $this->menuBoardCategory['menuCategoryId'] . '/product', [
'name' => 'Test Menu Board Category Delete',
'price' => '$11.11'
]);
$response = $this->sendRequest('DELETE', '/menuboard/' . $menuBoardProduct['menuProductId'] . '/product');
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\Tests\LocalWebTestCase;
class MenuBoardTest extends LocalWebTestCase
{
private $menuBoardId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
}
public function tearDown()
{
parent::tearDown();
if ($this->menuBoardId !== null) {
$this->getEntityProvider()->delete('/menuboard/' . $this->menuBoardId);
}
}
public function testListAll()
{
$response = $this->sendRequest('GET', '/menuboards');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
public function testAdd()
{
$name = Random::generateString(10, 'MenuBoard Add');
$response = $this->sendRequest('POST', '/menuboard', [
'name' => $name,
'description' => 'Description for test Menu Board Add'
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame('Description for test Menu Board Add', $object->data->description);
$this->menuBoardId = $object->id;
}
public function testEdit()
{
$menuBoard = $this->getEntityProvider()->post('/menuboard', [
'name' => 'Test Menu Board Edit',
'description' => 'Description for test Menu Board Edit'
]);
$name = Random::generateString(10, 'MenuBoard Edit');
$response = $this->sendRequest('PUT', '/menuboard/' . $menuBoard['menuId'], [
'name' => $name,
'description' => 'Test Menu Board Edited description'
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->name);
$this->assertSame('Test Menu Board Edited description', $object->data->description);
$this->menuBoardId = $object->id;
}
public function testDelete()
{
$menuBoard = $this->getEntityProvider()->post('/menuboard', [
'name' => 'Test Menu Board Delete',
'description' => 'Description for test Menu Board Delete'
]);
$response = $this->sendRequest('DELETE', '/menuboard/' . $menuBoard['menuId']);
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
}
}

View File

@@ -0,0 +1,196 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplayGroup;
use Xibo\OAuth2\Client\Entity\XiboNotification;
use Xibo\Tests\LocalWebTestCase;
class NotificationTest extends LocalWebTestCase
{
/** @var XiboDisplayGroup[] */
protected $startDisplayGroups;
/** @var XiboNotification[] */
protected $startNotifications;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startDisplayGroups = (new XiboDisplayGroup($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->startNotifications = (new XiboNotification($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all display groups that weren't there initially
$finalDisplayGroups = (new XiboDisplayGroup($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining display groups and nuke them
foreach ($finalDisplayGroups as $displayGroup) {
/** @var XiboDisplayGroup $displayGroup */
$flag = true;
foreach ($this->startDisplayGroups as $startGroup) {
if ($startGroup->displayGroupId == $displayGroup->displayGroupId) {
$flag = false;
}
}
if ($flag) {
try {
$displayGroup->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $displayGroup->displayGroupId . '. E:' . $e->getMessage());
}
}
}
// tearDown all notifications that weren't there initially
$finalNotifications = (new XiboNotification($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining notifications and nuke them
foreach ($finalNotifications as $notification) {
/** @var XiboNotification $notification */
$flag = true;
foreach ($this->startNotifications as $startNotf) {
if ($startNotf->notificationId == $notification->notificationId) {
$flag = false;
}
}
if ($flag) {
try {
$notification->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $notification->notificationId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* List notifications
*/
public function testListAll()
{
# Get all notifications
$response = $this->sendRequest('GET','/notification');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
/**
* Create new Notification
*/
public function testAdd()
{
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit group', 'notification description', 0, '');
# Add new notification and assign it to our display group
$subject = 'API Notification';
$response = $this->sendRequest('POST','/notification', [
'subject' => $subject,
'body' => 'Notification body text',
'releaseDt' => '2016-09-01 00:00:00',
'isEmail' => 0,
'isInterrupt' => 0,
'displayGroupIds' => [$displayGroup->displayGroupId]
// 'userGroupId' =>
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
# Check if the subject is correctly set
$this->assertSame($subject, $object->data->subject);
}
/**
* Delete notification
*/
public function testDelete()
{
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit group', 'phpunit description', 0, '');
# Create new notification
$notification = (new XiboNotification($this->getEntityProvider()))->create('API subject', 'API body', '2016-09-01 00:00:00', 0, 0, [$displayGroup->displayGroupId]);
# Delete notification
$response = $this->sendRequest('DELETE','/notification/' . $notification->notificationId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status);
# Clean up
$displayGroup->delete();
}
/**
* Edit notification
*/
public function testEdit()
{
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit group', 'phpunit description', 0, '');
# Create new notification
$notification = (new XiboNotification($this->getEntityProvider()))->create('API subject', 'API body', '2016-09-01 00:00:00', 0, 0, [$displayGroup->displayGroupId]);
$notification->releaseDt = Carbon::createFromTimestamp($notification->releaseDt)->format(DateFormatHelper::getSystemFormat());
# Create new subject
$subjectNew = 'Subject edited via API';
# Edit our notification
$response = $this->sendRequest('PUT','/notification/' . $notification->notificationId, [
'subject' => $subjectNew,
'body' => $notification->body,
'releaseDt' => $notification->releaseDt,
'isEmail' => $notification->isEmail,
'isInterrupt' => $notification->isInterrupt,
'displayGroupIds' => [$displayGroup->displayGroupId]
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
# Examine the returned object and check that it's what we expect
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($subjectNew, $object->data->subject);
# Clean up
$displayGroup->delete();
$notification->delete();
}
}

View File

@@ -0,0 +1,204 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Xibo\Entity\Display;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboDisplayProfile;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class PlayerSoftwareTest
* @package Xibo\Tests\integration
*/
class PlayerSoftwareTest extends LocalWebTestCase
{
use DisplayHelperTrait;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $media2;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplayProfile */
protected $displayProfile;
protected $version;
protected $version2;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) . ' Test');
// Upload version files
$uploadVersion = $this->uploadVersionFile(Random::generateString(), PROJECT_ROOT . '/tests/resources/Xibo_for_Android_v1.7_R61.apk');
$this->version = $uploadVersion['files'][0];
$uploadVersion2 = $this->uploadVersionFile(Random::generateString(), PROJECT_ROOT . '/tests/resources/Xibo_for_Android_v1.8_R108.apk');
$this->version2 = $uploadVersion2['files'][0];
// Create a Display
$this->display = $this->createDisplay(null, 'android');
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Create a display profile
$this->displayProfile = (new XiboDisplayProfile($this->getEntityProvider()))->create(Random::generateString(), 'android', 0);
// Edit display profile to add the uploaded apk to the config
$this->getEntityProvider()->put('/displayprofile/' . $this->displayProfile->displayProfileId, [
'name' => $this->displayProfile->name,
'type' => $this->displayProfile->type,
'isDefault' => $this->displayProfile->isDefault,
'versionMediaId' => $this->version['id']
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
// Delete the version files we've been working with
$this->getEntityProvider()->delete('/playersoftware/' . $this->version['id']);
$this->getEntityProvider()->delete('/playersoftware/' . $this->version2['id']);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the Display profile
$this->displayProfile->delete();
parent::tearDown();
}
// </editor-fold>
public function testVersionFromProfile()
{
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Edit display, assign it to the created display profile
$response = $this->sendRequest('PUT','/display/' . $this->display->displayId, [
'display' => $this->display->display,
'licensed' => $this->display->licensed,
'license' => $this->display->license,
'defaultLayoutId' => $this->display->defaultLayoutId,
'displayProfileId' => $this->displayProfile->displayProfileId,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded'] );
// Check response
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame($this->displayProfile->displayProfileId, $object->data->displayProfileId, $response->getBody());
// Ensure the Version Instructions are present on the Register Display call and that
// Register our display
$register = $this->getXmdsWrapper()->RegisterDisplay($this->display->license,
$this->display->license,
'android',
null,
null,
null,
'00:16:D9:C9:AL:69',
$this->display->xmrChannel,
$this->display->xmrPubKey
);
$this->getLogger()->debug($register);
$this->assertContains($this->version['fileName'], $register, 'Version information not in Register');
$this->assertContains('61', $register, 'Version information Code not in Register');
}
public function testVersionOverride()
{
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Edit display, set the versionMediaId
$response = $this->sendRequest('PUT','/display/' . $this->display->displayId, [
'display' => $this->display->display,
'licensed' => $this->display->licensed,
'license' => $this->display->license,
'versionMediaId' => $this->version2['id'],
'defaultLayoutId' => $this->display->defaultLayoutId,
'displayProfileId' => $this->displayProfile->displayProfileId
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded'] );
// Check response
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame($this->displayProfile->displayProfileId, $object->data->displayProfileId, $response->getBody());
$this->assertNotEmpty($object->data->overrideConfig);
foreach ($object->data->overrideConfig as $override) {
if ($override->name === 'versionMediaId') {
$this->assertSame($this->version2['id'], $override->value, json_encode($object->data->overrideConfig));
}
}
// call register
$register = $this->getXmdsWrapper()->RegisterDisplay($this->display->license,
$this->display->license,
'android',
null,
null,
null,
'00:16:D9:C9:AL:69',
$this->display->xmrChannel,
$this->display->xmrPubKey
);
// make sure the media ID set on the display itself is in the register
$this->getLogger()->debug($register);
$this->assertContains($this->version2['fileName'], $register, 'Version information not in Register');
$this->assertContains('108', $register, 'Version information Code not in Register');
}
private function uploadVersionFile($fileName, $filePath)
{
$payload = [
[
'name' => 'name',
'contents' => $fileName
],
[
'name' => 'files',
'contents' => fopen($filePath, 'r')
]
];
return $this->getEntityProvider()->post('/playersoftware', ['multipart' => $payload]);
}
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\Tests\LocalWebTestCase;
/**
* Class PlaylistTest
* @package Xibo\Tests\integration
*/
class PlaylistTest extends LocalWebTestCase
{
/** @var XiboPlaylist[] */
private $playlists;
/** @var XiboPlaylist */
private $duplicateName;
public function setup()
{
parent::setup();
$this->duplicateName = (new XiboPlaylist($this->getEntityProvider()))->hydrate($this->getEntityProvider()->post('/playlist', [
'name' => Random::generateString(5, 'playlist')
]));
// Add a Playlist to use for the duplicate name test
$this->playlists[] = $this->duplicateName;
}
public function tearDown()
{
$this->getLogger()->debug('Tearing down, removing ' . count($this->playlists));
// Delete any Playlists we've added
foreach ($this->playlists as $playlist) {
$this->getEntityProvider()->delete('/playlist/' . $playlist->playlistId);
}
parent::tearDown();
}
/**
* @return array
*/
public function addPlaylistCases()
{
return [
'Normal add' => [200, Random::generateString(5, 'playlist'), null, 0, null, null],
'Tags add' => [200, Random::generateString(5, 'playlist'), 'test', 0, null, null],
'Dynamic add' => [200, Random::generateString(5, 'playlist'), null, 1, 'test', null]
];
}
/**
* @return array
*/
public function addPlaylistFailureCases()
{
return [
'Dynamic without filter' => [422, Random::generateString(5, 'playlist'), null, 1, null, null],
'Without a name' => [422, null, null, 1, null, null]
];
}
/**
* @dataProvider addPlaylistCases
*/
public function testAddPlaylist($statusCode, $name, $tags, $isDynamic, $nameFilter, $tagFilter)
{
// Add this Playlist
$response = $this->sendRequest('POST', '/playlist', [
'name' => $name,
'tags' => $tags,
'isDynamic' => $isDynamic,
'filterMediaName' => $nameFilter,
'filterMediaTag' => $tagFilter
]);
// Check the response headers
$this->assertSame($statusCode, $response->getStatusCode(), 'Not successful: ' . $response->getStatusCode() . $response->getBody());
// Make sure we have a useful body
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, 'Missing data');
$this->assertObjectHasAttribute('id', $object, 'Missing id');
// Add to the list of playlists to clean up
if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) {
$this->playlists[] = (new XiboPlaylist($this->getEntityProvider()))->hydrate((array)$object->data);
}
// Get the Playlists back out from the API, to double check it has been created as we expected
/** @var XiboPlaylist $playlistCheck */
$playlistCheck = (new XiboPlaylist($this->getEntityProvider()))->hydrate($this->getEntityProvider()->get('/playlist', ['playlistId' => $object->id])[0]);
$this->assertEquals($name, $playlistCheck->name, 'Names are not identical');
}
/**
* @dataProvider addPlaylistFailureCases
*/
public function testAddPlaylistFailure($statusCode, $name, $tags, $isDynamic, $nameFilter, $tagFilter)
{
$response = $this->sendRequest('POST', '/playlist', [
'name' => $name,
'tags' => $tags,
'isDynamic' => $isDynamic,
'filterMediaName' => $nameFilter,
'filterMediaTag' => $tagFilter
]);
// Check the response headers
$this->assertSame($statusCode, $response->getStatusCode(), 'Not successful: ' . $response->getStatusCode() . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, 'Missing data');
$this->assertSame([], $object->data);
$this->assertObjectHasAttribute('error', $object, 'Missing error');
$this->assertObjectNotHasAttribute('id', $object);
}
/**
* Edit test
*/
public function testEditPlaylist()
{
// New name
$newName = Random::generateString(5, 'playlist');
// Take the duplicate name playlist, and edit it
$response = $this->sendRequest('PUT','/playlist/' . $this->duplicateName->playlistId, [
'name' => $newName,
'tags' => null,
'isDynamic' => 0,
'nameFilter' => null,
'tagFilter' => null
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
// Check the response headers
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getStatusCode() . $response->getBody());
/** @var XiboPlaylist $playlistCheck */
$playlistCheck = (new XiboPlaylist($this->getEntityProvider()))->hydrate($this->getEntityProvider()->get('/playlist', ['playlistId' => $this->duplicateName->playlistId])[0]);
$this->assertEquals($newName, $playlistCheck->name, 'Names are not identical');
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\Tests\LocalWebTestCase;
/**
* Class PlaylistTest
* @package Xibo\Tests\Integration
*/
class PlaylistWidgetListTest extends LocalWebTestCase
{
/** @var XiboPlaylist */
private $playlist;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// Create a Playlist
$this->playlist = (new XiboPlaylist($this->getEntityProvider()))->hydrate($this->getEntityProvider()->post('/playlist', [
'name' => Random::generateString(5, 'playlist')
]));
// Assign some Widgets
$this->getEntityProvider()->post('/playlist/widget/clock/' . $this->playlist->playlistId, [
'duration' => 100,
'useDuration' => 1
]);
$text = $this->getEntityProvider()->post('/playlist/widget/text/' . $this->playlist->playlistId);
$this->getEntityProvider()->put('/playlist/widget/' . $text['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Playlist
parent::tearDown();
}
/**
* List all items in playlist
*/
public function testGetWidget()
{
// Search widgets on our playlist
$response = $this->sendRequest('GET','/playlist/widget', [
'playlistId' => $this->playlist->playlistId
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame(2, $object->data->recordsTotal);
}
}

View File

@@ -0,0 +1,295 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboImage;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ProofOfPlayOnOff
* @package Xibo\Tests\Integration
*/
class ProofOfPlayOnOff extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboLayout */
protected $layout2;
/** @var XiboLibrary */
protected $media;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// get draft Layout
$layout = $this->getDraft($this->layout);
// Create another Layout with stat enabled
$this->layout2 = (new XiboLayout($this->getEntityProvider()))->create(
Random::generateString(8, 'phpunit'),
'phpunit layout',
'',
$this->getResolutionId('landscape'),
1
);
// Upload some media
$this->media = (new XiboLibrary($this->getEntityProvider()))->create('API video '.rand(1,400), PROJECT_ROOT . '/tests/resources/HLH264.mp4');
// Assign the media we've created to our regions playlist.
$playlist = (new XiboPlaylist($this->getEntityProvider()))->assign([$this->media->mediaId], 10, $layout->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
$this->getLogger()->debug('Finished Setup');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
$this->getLogger()->debug('Tear down for ' . get_class($this) . ' Test');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the second Layout we've been working with
$this->deleteLayout($this->layout2);
// Delete the media record
$this->media->deleteAssigned();
}
/**
* Each array is a test run
* Format (enableStat)
* @return array
*/
public function enableStatLayoutCases()
{
return [
// various correct enableStat flag
'Layout enableStat Off' => [0],
'Layout enableStat On' => [1]
];
}
/**
* Each array is a test run
* Format (enableStat)
* @return array
*/
public function enableStatMediaAndWidgetCases()
{
return [
// various correct enableStat options - for both media and widget are same
'enableStat Off' => ['Off'],
'enableStat On' => ['On'],
'enableStat Inherit' => ['Inherit']
];
}
/**
* Add enableStat flag was set to 0 when creating the layout
*/
public function testAddLayoutEnableStatOff()
{
// Check that the layout enable stat sets to off
$layout = (new XiboLayout($this->getEntityProvider()))->getById($this->layout->layoutId);
$this->assertSame(0, $layout->enableStat);
}
/**
* Add enableStat flag was set to 1 when creating the layout
*/
public function testAddLayoutEnableStatOn()
{
// Check that the layout enable stat sets to on
$layout = (new XiboLayout($this->getEntityProvider()))->getById($this->layout2->layoutId);
$this->assertSame(1, $layout->enableStat);
}
/**
* Edit enableStat flag of an existing layout
* @dataProvider enableStatLayoutCases
*/
public function testEditLayoutEnableStat($enableStat)
{
$name = Random::generateString(8, 'phpunit');
$description = Random::generateString(8, 'description');
$response = $this->sendRequest('PUT','/layout/' . $this->layout->layoutId, [
'name' => $name,
'description' => $description,
'enableStat' => $enableStat
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame($enableStat, $object->data->enableStat);
// Check that the layout enable stat sets to on/off
$layout = (new XiboLayout($this->getEntityProvider()))->getById($object->id);
$this->assertSame($enableStat, $layout->enableStat);
}
/**
* Edit enableStat flag of an existing media file
* @dataProvider enableStatMediaAndWidgetCases
*/
public function testEditMediaEnableStat($enableStat)
{
$name = Random::generateString(8, 'phpunit');
// Edit media file
$response = $this->sendRequest('PUT','/library/' . $this->media->mediaId, [
'name' => $name,
'duration' => 50,
'updateInLayouts' => 1,
'enableStat' => $enableStat
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame($enableStat, $object->data->enableStat);
$media = (new XiboLibrary($this->getEntityProvider()))->getById($this->media->mediaId);
$this->assertSame($enableStat, $media->enableStat);
}
/**
* @throws \Xibo\OAuth2\Client\Exception\XiboApiException
* @dataProvider enableStatMediaAndWidgetCases
*/
public function testEditWidgetEnableStat($enableStat)
{
// Now try to edit our assigned Media Item.
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'enableStat' => $enableStat,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']
);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboImage $widgetOptions */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$widgetOptions = (new XiboImage($this->getEntityProvider()))->hydrate($response[0]);
foreach ($widgetOptions->widgetOptions as $option) {
if ($option['option'] == 'enableStat') {
$this->assertSame($enableStat, $option['value']);
}
}
}
/**
* Copy Layout - enableStat flag copied from an existing layout
*/
public function testCopyLayoutCheckEnableStat()
{
// Generate new random name
$nameCopy = Random::generateString(8, 'phpunit');
// Call copy
$response = $this->sendRequest('POST','/layout/copy/' . $this->layout2->layoutId, [
'name' => $nameCopy,
'description' => 'Copy',
'copyMediaFiles' => 1
], [
'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
]);
$this->assertSame(200, $response->getStatusCode());
// Check if copied layout has enableStat flag of copying layout
$object = json_decode($response->getBody());
$this->assertSame($this->layout2->enableStat, $object->data->enableStat);
}
/**
* Bulk On/Off Layout enableStat
* @dataProvider enableStatLayoutCases
*/
public function testLayoutBulkEnableStat($enableStat)
{
// Call Set enable stat
$response = $this->sendRequest('PUT','/layout/setenablestat/' . $this->layout->layoutId, [
'enableStat' => $enableStat
], [
'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
]);
$this->assertSame(200, $response->getStatusCode());
$layout = (new XiboLayout($this->getEntityProvider()))->getById($this->layout->layoutId);
$this->assertSame($enableStat, $layout->enableStat);
}
/**
* Bulk On/Off/Inherit Media enableStat
* @dataProvider enableStatMediaAndWidgetCases
*/
public function testMediaBulkEnableStat($enableStat)
{
// Call Set enable stat
$response = $this->sendRequest('PUT','/library/setenablestat/' . $this->media->mediaId, [
'enableStat' => $enableStat
], [
'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
]);
$this->assertSame(200, $response->getStatusCode());
$media = (new XiboLibrary($this->getEntityProvider()))->getById($this->media->mediaId);
$this->assertSame($enableStat, $media->enableStat);
}
}

View File

@@ -0,0 +1,188 @@
<?php
/*
* Copyright (C) 2023 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ReportScheduleDataTest
* @package Xibo\Tests\Integration
*/
class ReportScheduleDataTest extends LocalWebTestCase
{
use LayoutHelperTrait, DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
// Stat type
private $type;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// Create a Layout
$this->layout = $this->createLayout();
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetLicensed($this->display);
$this->type = 'layout';
$hardwareId = $this->display->license;
// Record some stats
$this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(4)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(3)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$this->type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'" />
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(3)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$this->type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"/>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays()->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$this->type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"/>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays()->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$this->type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'" />
</stats>'
);
$this->getLogger()->debug('Finished Setup');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete stat records
self::$container->get('timeSeriesStore')
->deleteStats(Carbon::now(), Carbon::now()->startOfDay()->subDays(10));
}
/**
* Check if proof of play statistics are correct
*/
public function testProofOfPlayReportYesterday()
{
$response = $this->sendRequest('GET', '/report/data/proofofplayReport', [
'reportFilter'=> 'yesterday',
'groupByFilter' => 'byday',
'displayId' => $this->display->displayId,
'layoutId' => [$this->layout->layoutId],
'type' => $this->type
], ['HTTP_ACCEPT'=>'application/json'], 'web', true);
$this->getLogger()->debug('Response code is: ' . $response->getStatusCode());
$body = $response->getBody();
$this->getLogger()->debug($body);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($body);
$object = json_decode($body);
$this->assertObjectHasAttribute('table', $object, $body);
$this->assertSame(1, $object->table[0]->numberPlays);
}
/**
* Check if proof of play statistics are correct for Proof of play Report
*/
public function testProofOfPlayReport()
{
$response = $this->sendRequest('GET', '/report/data/proofofplayReport', [
'statsFromDt' => Carbon::now()->startOfDay()->subDays(3)->format(DateFormatHelper::getSystemFormat()),
'statsToDt' => Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()),
'groupByFilter' => 'byday',
'displayId' => $this->display->displayId,
'layoutId' => [$this->layout->layoutId],
'type' => $this->type
], ['HTTP_ACCEPT'=>'application/json'], 'web', true);
$this->assertSame(200, $response->getStatusCode());
$body = $response->getBody();
$this->getLogger()->debug($body);
$this->assertNotEmpty($body);
$object = json_decode($body);
$this->assertObjectHasAttribute('table', $object, $response->getBody());
$this->assertSame(1, $object->table[0]->numberPlays);
}
/**
* Check if proof of play statistics are correct for Summary Report
*/
public function testSummaryReport()
{
$response = $this->sendRequest('GET', '/report/data/summaryReport', [
'statsFromDt' => Carbon::now()->startOfDay()->subDays(4)->format(DateFormatHelper::getSystemFormat()),
'statsToDt' => Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()),
'groupByFilter' => 'byday',
'displayId' => $this->display->displayId,
'layoutId' => $this->layout->layoutId,
'type' => $this->type
], ['HTTP_ACCEPT'=>'application/json'], 'web', true);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('chart', $object, $response->getBody());
$expectedSeconds = Carbon::now()->startOfDay()->subDays(3)->format('U') -
Carbon::now()->startOfDay()->subDays(4)->format('U');
$this->assertSame($expectedSeconds, $object->chart->data->datasets[0]->data[0]);
}
}

View File

@@ -0,0 +1,208 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboReportSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ReportScheduleTest
* @package Xibo\Tests\Integration
*/
class ReportScheduleTest extends LocalWebTestCase
{
use LayoutHelperTrait, DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// Create a Layout
$this->layout = $this->createLayout();
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
/**
* Each array is a test run
* Format (filter, reportName)
* @return array
*/
public function filterCreateCases()
{
return [
'proofofplayReport Daily' => ['daily', 'proofofplayReport'],
'proofofplayReport Weekly' => ['weekly', 'proofofplayReport'],
'proofofplayReport Monthly' => ['monthly', 'proofofplayReport'],
'proofofplayReport Yearly' => ['yearly', 'proofofplayReport'],
'summaryReport Daily' => ['daily', 'summaryReport'],
'summaryReport Weekly' => ['weekly', 'summaryReport'],
'summaryReport Monthly' => ['monthly', 'summaryReport'],
'summaryReport Yearly' => ['yearly', 'summaryReport'],
'distributionReport Daily' => ['daily', 'distributionReport'],
'distributionReport Weekly' => ['weekly', 'distributionReport'],
'distributionReport Monthly' => ['monthly', 'distributionReport'],
'distributionReport Yearly' => ['yearly', 'distributionReport'],
];
}
/**
* Create Report Schedule
* @dataProvider filterCreateCases
*/
public function testCreateReportSchedule($filter, $report)
{
$reportSchedule = (new XiboReportSchedule($this->getEntityProvider()))
->create('Report Schedule', $report, $filter, 'byhour', null,
$this->display->displayId, '{"type":"layout","selectedId":'.$this->layout->layoutId.',"eventTag":null}');
$this->assertSame($report, $reportSchedule->reportName);
// Delete Report Schedule
$reportSchedule->delete();
}
/**
* Report Schedule Delete All Saved Report
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function testReportScheduleDeleteAllSavedReport()
{
$reportSchedule = (new XiboReportSchedule($this->getEntityProvider()))
->create('Report Schedule', 'proofofplayReport', 'daily');
$reportScheduleId = $reportSchedule->reportScheduleId;
$task = $this->getTask('\Xibo\XTR\ReportScheduleTask');
$task->run();
self::$container->get('store')->commitIfNecessary();
// Delete All Saved Report
$resDelete = $this->sendRequest('POST', '/report/reportschedule/' .
$reportScheduleId. '/deletesavedreport');
$this->assertSame(200, $resDelete->getStatusCode(), $resDelete->getBody());
// Delete Report Schedule
$reportSchedule->delete();
}
/**
* Report Schedule Toggle Active
*/
public function testReportScheduleToggleActive()
{
$reportSchedule = (new XiboReportSchedule($this->getEntityProvider()))
->create('Report Schedule', 'proofofplayReport', 'daily');
// Toggle Active
$response = $this->sendRequest('POST', '/report/reportschedule/'.
$reportSchedule->reportScheduleId.'/toggleactive');
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame('Paused Report Schedule', $object->message);
// Delete Report Schedule
$reportSchedule->delete();
}
/**
* Report Schedule Reset
*/
public function testReportScheduleReset()
{
$reportSchedule = (new XiboReportSchedule($this->getEntityProvider()))
->create('Report Schedule', 'proofofplayReport', 'daily');
// Reset
$response = $this->sendRequest('POST', '/report/reportschedule/'.
$reportSchedule->reportScheduleId.'/reset');
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertSame('Success', $object->message);
// Delete Report Schedule
$reportSchedule->delete();
}
/**
* Delete Saved Report
* @throws \Xibo\OAuth2\Client\Exception\XiboApiException|\Xibo\Support\Exception\NotFoundException
*/
public function testDeleteSavedReport()
{
$reportSchedule = (new XiboReportSchedule($this->getEntityProvider()))
->create('Report Schedule', 'proofofplayReport', 'daily');
// Create a saved report
$task = $this->getTask('\Xibo\XTR\ReportScheduleTask');
$task->run();
self::$container->get('store')->commitIfNecessary();
// Get updated report schedule's last saved report Id
$rs = (new XiboReportSchedule($this->getEntityProvider()))
->getById($reportSchedule->reportScheduleId);
// Delete Saved Report
$response = $this->sendRequest('DELETE', '/report/savedreport/'.
$rs->lastSavedReportId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
// Delete Report Schedule
$rs->delete();
}
}

View File

@@ -0,0 +1,232 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboResolution;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ResolutionTest
* @package Xibo\Tests\Integration
*/
class ResolutionTest extends LocalWebTestCase
{
protected $startResolutions;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startResolutions = (new XiboResolution($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all resolutions that weren't there initially
$finalResolutions = (new XiboResolution($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining resolutions and nuke them
foreach ($finalResolutions as $resolution) {
/** @var XiboResolution $resolution */
$flag = true;
foreach ($this->startResolutions as $startRes) {
if ($startRes->resolutionId == $resolution->resolutionId) {
$flag = false;
}
}
if ($flag) {
try {
$resolution->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $resolution->resolutionId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
public function testListAll()
{
$response = $this->sendRequest('GET','/resolution');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
}
/**
* testAddSuccess - test adding various Resolutions that should be valid
* @dataProvider provideSuccessCases
* @group minimal
*/
public function testAddSuccess($resolutionName, $resolutionWidth, $resolutionHeight)
{
# Loop through any pre-existing resolutions to make sure we're not
# going to get a clash
foreach ($this->startResolutions as $tmpRes) {
if ($tmpRes->resolution == $resolutionName) {
$this->skipTest("There is a pre-existing resolution with this name");
return;
}
}
# Create new resolutions with data from provideSuccessCases
$response = $this->sendRequest('POST','/resolution', [
'resolution' => $resolutionName,
'width' => $resolutionWidth,
'height' => $resolutionHeight
]);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($resolutionName, $object->data->resolution);
$this->assertSame($resolutionWidth, $object->data->width);
$this->assertSame($resolutionHeight, $object->data->height);
# Check that the resolution was added correctly
$resolution = (new XiboResolution($this->getEntityProvider()))->getById($object->id);
$this->assertSame($resolutionName, $resolution->resolution);
$this->assertSame($resolutionWidth, $resolution->width);
$this->assertSame($resolutionHeight, $resolution->height);
# Clean up the Resolutions as we no longer need it
$this->assertTrue($resolution->delete(), 'Unable to delete ' . $resolution->resolutionId);
}
/**
* Each array is a test run
* Format (resolution name, width, height)
* @return array
*/
public function provideSuccessCases()
{
# Sets of correct data, which should be successfully added
return [
'resolution 1' => ['test resolution', 800, 200],
'resolution 2' => ['different test resolution', 1069, 1699]
];
}
/**
* testAddFailure - test adding various resolutions that should be invalid
* @dataProvider provideFailureCases
*/
public function testAddFailure($resolutionName, $resolutionWidth, $resolutionHeight)
{
# create new resolution with data from provideFailureCases
$response = $this->sendRequest('POST','/resolution', [
'resolution' => $resolutionName,
'width' => $resolutionWidth,
'height' => $resolutionHeight
]);
# Check if it fails as expected
$this->assertSame(422, $response->getStatusCode(), 'Expecting failure, received ' . $response->getStatusCode());
}
/**
* Each array is a test run
* Format (resolution name, width, height)
* @return array
*/
public function provideFailureCases()
{
# Sets of incorrect data, which should lead to a failure
return [
'incorrect width and height' => ['wrong parameters', 'abc', NULL],
'incorrect width' => [12, 'width', 1699]
];
}
/**
* Edit an existing resolution
* @group minimal
*/
public function testEdit()
{
# Load in a known resolution
/** @var XiboResolution $resolution */
$resolution = (new XiboResolution($this->getEntityProvider()))->create('phpunit resolution', 1200, 860);
$newWidth = 2400;
# Change the resolution name, width and enable flag
$name = Random::generateString(8, 'phpunit');
$response = $this->sendRequest('PUT','/resolution/' . $resolution->resolutionId, [
'resolution' => $name,
'width' => $newWidth,
'height' => $resolution->height,
'enabled' => 0
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
# Examine the returned object and check that it's what we expect
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$this->assertSame($name, $object->data->resolution);
$this->assertSame($newWidth, $object->data->width);
$this->assertSame(0, $object->data->enabled);
# Check that the resolution was actually renamed
$resolution = (new XiboResolution($this->getEntityProvider()))->getById($object->id);
$this->assertSame($name, $resolution->resolution);
$this->assertSame($newWidth, $resolution->width);
# Clean up the resolution as we no longer need it
$resolution->delete();
}
/**
* Test delete
* @group minimal
*/
public function testDelete()
{
# Generate two random names
$name1 = Random::generateString(8, 'phpunit');
$name2 = Random::generateString(8, 'phpunit');
# Load in a couple of known resolutions
$res1 = (new XiboResolution($this->getEntityProvider()))->create($name1, 1000, 500);
$res2 = (new XiboResolution($this->getEntityProvider()))->create($name2, 2000, 760);
# Delete the one we created last
$response = $this->sendRequest('DELETE','/resolution/' . $res2->resolutionId);
# This should return 204 for success
$object = json_decode($response->getBody());
$this->assertSame(204, $object->status, $response->getBody());
# Check only one remains
$resolutions = (new XiboResolution($this->getEntityProvider()))->get();
$this->assertEquals(count($this->startResolutions) + 1, count($resolutions));
$flag = false;
foreach ($resolutions as $res) {
if ($res->resolutionId == $res1->resolutionId) {
$flag = true;
}
}
$this->assertTrue($flag, 'Resolution ID ' . $res1->resolutionId . ' was not found after deleting a different Resolution');
# Clean up
$res1->delete();
}
}

View File

@@ -0,0 +1,188 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDaypart;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
class ScheduleDayPartTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $layout;
/** @var \Xibo\OAuth2\Client\Entity\XiboDisplay */
protected $display;
/** @var \Xibo\OAuth2\Client\Entity\XiboSchedule */
protected $event;
/** @var \Xibo\OAuth2\Client\Entity\XiboDaypart */
protected $dayPart;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Make sure the Layout Status is as we expect
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Create a Day Part
// calculate a few hours either side of now
// must be tomorrow
// must not cross the day boundary
$now = Carbon::now()->startOfDay()->addDay()->addHour();
$this->dayPart = (new XiboDaypart($this->getEntityProvider()))->create(
Random::generateString(5),
'',
$now->format('H:i'),
$now->copy()->addHours(5)->format('H:i')
);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the DayPart
$this->dayPart->delete();
}
// </editor-fold>
public function testSchedule()
{
// Our CMS is in GMT
// Create a schedule one hours time in my player timezone
$date = Carbon::now()->addDay()->setTime(0,0,0);
$this->getLogger()->debug('Event start will be at: ' . $date->format(DateFormatHelper::getSystemFormat()));
$response = $this->sendRequest('POST','/schedule', [
'fromDt' => $date->format(DateFormatHelper::getSystemFormat()),
'dayPartId' => $this->dayPart->dayPartId,
'eventTypeId' => 1,
'campaignId' => $this->layout->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 0,
'scheduleRecurrenceType' => null,
'scheduleRecurrenceDetail' => null,
'scheduleRecurrenceRange' => null,
'syncTimezone' => 0
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->Schedule($this->display->license));
//$this->getLogger()->debug($xml->saveXML());
// Check our event is present.
$layouts = $xml->getElementsByTagName('layout');
$this->assertTrue(count($layouts) == 1, 'Unexpected number of events');
foreach ($layouts as $layout) {
$xmlFromDt = $layout->getAttribute('fromdt');
$xmlToDt = $layout->getAttribute('todt');
$this->assertEquals($date->format('Y-m-d') . ' ' . $this->dayPart->startTime . ':00', $xmlFromDt, 'From date doesnt match: ' . $xmlFromDt);
$this->assertEquals($date->format('Y-m-d') . ' ' . $this->dayPart->endTime . ':00', $xmlToDt, 'To date doesnt match: ' . $xmlToDt);
}
// Also check this layout is in required files.
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->RequiredFiles($this->display->license));
//$this->getLogger()->debug($xml->saveXML());
// Find using XPATH
$xpath = new \DOMXPath($xml);
$nodes =$xpath->query('//file[@type="layout"]');
$this->assertGreaterThanOrEqual(1, $nodes->count(), 'Layout not in required files');
$found = false;
foreach ($nodes as $node) {
/** @var \DOMNode $node */
if ($this->layout->layoutId == $node->attributes->getNamedItem('id')->nodeValue) {
$found = true;
break;
}
}
if (!$found) {
$this->fail('Layout not found in Required Files XML');
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ScheduleDisplayDeleteTest
* @package Xibo\Tests\Integration
*/
class ScheduleDisplayDeleteTest extends LocalWebTestCase
{
use DisplayHelperTrait;
use LayoutHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboDisplay */
protected $display2;
/** @var XiboSchedule */
protected $event;
// <editor-fold desc="Init">
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// We need 2 displays
$this->display = $this->createDisplay();
$this->display2 = $this->createDisplay();
// This is the remaining display we will test for the schedule
$this->displaySetLicensed($this->display2);
// 1 Layout
$this->layout = $this->createLayout();
// 1 Schedule
$this->event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$this->layout->campaignId,
[$this->display->displayGroupId, $this->display2->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout and Remaining Display
$this->deleteDisplay($this->display2);
$this->deleteLayout($this->layout);
parent::tearDown();
}
//</editor-fold>
/**
* Do the test
*/
public function test()
{
// Delete 1 display
$this->sendRequest('DELETE','/display/' . $this->display->displayId);
// Test to ensure the schedule remains
$schedule = $this->getXmdsWrapper()->Schedule($this->display2->license);
$this->assertContains('file="' . $this->layout->layoutId . '"', $schedule, 'Layout not scheduled');
}
}

View File

@@ -0,0 +1,246 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ScheduleNotificationTest
* @package Xibo\Tests\Integration
*/
class ScheduleNotificationTest extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $layout;
/** @var \Xibo\OAuth2\Client\Entity\XiboDisplay */
protected $display;
/** @var \Xibo\OAuth2\Client\Entity\XiboSchedule */
protected $event;
protected $timeZone = 'Asia/Hong_Kong';
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetTimezone($this->display, $this->timeZone);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Make sure the Layout Status is as we expect
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Check our timzone is set correctly
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->RegisterDisplay($this->display->license, $this->timeZone));
$this->assertEquals($this->timeZone, $xml->documentElement->getAttribute('localTimezone'), 'Timezone not correct');
$xml = null;
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
public function testSchedule()
{
// Our CMS is in GMT
// Create a schedule one hours time in my player timezone
$localNow = Carbon::now()->setTimezone($this->timeZone);
$date = $localNow->copy()->addHour()->startOfHour();
$this->getLogger()->debug('Event start will be at: ' . $date->format(DateFormatHelper::getSystemFormat()));
$response = $this->sendRequest('POST','/schedule', [
'fromDt' => $date->format(DateFormatHelper::getSystemFormat()),
'toDt' => $date->copy()->addMinutes(30)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $this->layout->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 0,
'recurrenceType' => null,
'recurrenceDetail' => null,
'recurrenceRange' => null,
'syncTimezone' => 0,
'scheduleReminders' => [
[
'reminder_value' => 1,
'reminder_type' => 1,
'reminder_option' => 1,
'reminder_isEmailHidden' => 1
],
[
'reminder_value' => 1,
'reminder_type' => 1,
'reminder_option' => 2,
'reminder_isEmailHidden' => 1
]
],
'embed' => 'scheduleReminders'
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if two reminders are created
$this->assertSame(2, count($object->data->scheduleReminders));
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->Schedule($this->display->license));
//$this->getLogger()->debug($xml->saveXML());
// Check the filter from and to dates are correct
$this->assertEquals($localNow->startOfHour()->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterFrom'), 'Filter from date incorrect');
$this->assertEquals($localNow->addDays(2)->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterTo'), 'Filter to date incorrect');
// Check our event is present.
$layouts = $xml->getElementsByTagName('layout');
$this->assertTrue(count($layouts) == 1, 'Unexpected number of events');
foreach ($layouts as $layout) {
$xmlFromDt = $layout->getAttribute('fromdt');
$this->assertEquals($date->format(DateFormatHelper::getSystemFormat()), $xmlFromDt, 'From date doesnt match: ' . $xmlFromDt);
}
}
public function testRecurringSchedule()
{
// Our CMS is in GMT
// Create a schedule one hours time in my player timezone
// we start this schedule the day before
$localNow = Carbon::now()->setTimezone($this->timeZone);
$date = $localNow->copy()->subDay()->addHour()->startOfHour();
$this->getLogger()->debug('Event start will be at: ' . $date->format(DateFormatHelper::getSystemFormat()));
$response = $this->sendRequest('POST','/schedule', [
'fromDt' => $date->format(DateFormatHelper::getSystemFormat()),
'toDt' => $date->copy()->addMinutes(30)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $this->layout->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 0,
'recurrenceType' => 'Day',
'recurrenceDetail' => 1,
'recurrenceRange' => null,
'syncTimezone' => 0,
'scheduleReminders' => [
[
'reminder_value' => 1,
'reminder_type' => 1,
'reminder_option' => 1,
'reminder_isEmailHidden' => 1
],
[
'reminder_value' => 1,
'reminder_type' => 1,
'reminder_option' => 2,
'reminder_isEmailHidden' => 1
]
],
'embed' => 'scheduleReminders'
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if two reminders are created
$this->assertSame(2, count($object->data->scheduleReminders));
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->Schedule($this->display->license));
//$this->getLogger()->debug($xml->saveXML());
// Check the filter from and to dates are correct
$this->assertEquals($localNow->startOfHour()->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterFrom'), 'Filter from date incorrect');
$this->assertEquals($localNow->addDays(2)->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterTo'), 'Filter to date incorrect');
// Check our event is present.
$layouts = $xml->getElementsByTagName('layout');
foreach ($layouts as $layout) {
// Move our day on (we know we're recurring by day), and that we started a day behind
$date->addDay();
$xmlFromDt = $layout->getAttribute('fromdt');
$this->assertEquals($date->format(DateFormatHelper::getSystemFormat()), $xmlFromDt, 'From date doesnt match: ' . $xmlFromDt);
}
}
}

View File

@@ -0,0 +1,404 @@
<?php
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboCampaign;
use Xibo\OAuth2\Client\Entity\XiboCommand;
use Xibo\OAuth2\Client\Entity\XiboDisplayGroup;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboSchedule;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ScheduleTest
* @package Xibo\Tests\Integration
*/
class ScheduleTest extends LocalWebTestCase
{
use LayoutHelperTrait;
protected $route = '/schedule';
protected $startCommands;
protected $startDisplayGroups;
protected $startEvents;
protected $startLayouts;
protected $startCampaigns;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->startDisplayGroups = (new XiboDisplayGroup($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->startLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->startCampaigns = (new XiboCampaign($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
$this->startCommands = (new XiboCommand($this->getEntityProvider()))->get(['start' => 0, 'length' => 1000]);
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// tearDown all display groups that weren't there initially
$finalDisplayGroups = (new XiboDisplayGroup($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining display groups and nuke them
foreach ($finalDisplayGroups as $displayGroup) {
/** @var XiboDisplayGroup $displayGroup */
$flag = true;
foreach ($this->startDisplayGroups as $startGroup) {
if ($startGroup->displayGroupId == $displayGroup->displayGroupId) {
$flag = false;
}
}
if ($flag) {
try {
$displayGroup->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Unable to delete ' . $displayGroup->displayGroupId . '. E:' . $e->getMessage());
}
}
}
// tearDown all layouts that weren't there initially
$finalLayouts = (new XiboLayout($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining layouts and nuke them
foreach ($finalLayouts as $layout) {
/** @var XiboLayout $layout */
$flag = true;
foreach ($this->startLayouts as $startLayout) {
if ($startLayout->layoutId == $layout->layoutId) {
$flag = false;
}
}
if ($flag) {
try {
$layout->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Layout: Unable to delete ' . $layout->layoutId . '. E:' . $e->getMessage());
}
}
}
// tearDown all campaigns that weren't there initially
$finalCamapigns = (new XiboCampaign($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining campaigns and nuke them
foreach ($finalCamapigns as $campaign) {
/** @var XiboCampaign $campaign */
$flag = true;
foreach ($this->startCampaigns as $startCampaign) {
if ($startCampaign->campaignId == $campaign->campaignId) {
$flag = false;
}
}
if ($flag) {
try {
$campaign->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Campaign: Unable to delete ' . $campaign->campaignId . '. E:' . $e->getMessage());
}
}
}
// tearDown all commands that weren't there initially
$finalCommands = (new XiboCommand($this->getEntityProvider()))->get(['start' => 0, 'length' => 10000]);
# Loop over any remaining commands and nuke them
foreach ($finalCommands as $command) {
/** @var XiboCommand $command */
$flag = true;
foreach ($this->startCommands as $startCom) {
if ($startCom->commandId == $command->commandId) {
$flag = false;
}
}
if ($flag) {
try {
$command->delete();
} catch (\Exception $e) {
fwrite(STDERR, 'Command: Unable to delete ' . $command->commandId . '. E:' . $e->getMessage());
}
}
}
parent::tearDown();
}
/**
* testListAll
*/
public function testListAll()
{
# list all scheduled events
$response = $this->sendRequest('GET','/schedule/data/events');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('result', $object, $response->getBody());
}
/**
* @group add
* @dataProvider provideSuccessCasesCampaign
*/
public function testAddEventCampaign($isCampaign, $scheduleFrom, $scheduleTo, $scheduledayPartId, $scheduleRecurrenceType, $scheduleRecurrenceDetail, $scheduleRecurrenceRange, $scheduleOrder, $scheduleIsPriority)
{
# Create new display group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit group', 'phpunit description', 0, '');
$layout = null;
$campaign = null;
# isCampaign checks if we want to add campaign or layout to our event
if ($isCampaign) {
# Create Campaign
/* @var XiboCampaign $campaign */
$campaign = (new XiboCampaign($this->getEntityProvider()))->create('phpunit');
# Create new event with data from provideSuccessCasesCampaign where isCampaign is set to true
$response = $this->sendRequest('POST', $this->route, [
'fromDt' => Carbon::createFromTimestamp($scheduleFrom)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::createFromTimestamp($scheduleTo)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $campaign->campaignId,
'displayGroupIds' => [$displayGroup->displayGroupId],
'displayOrder' => $scheduleOrder,
'isPriority' => $scheduleIsPriority,
'scheduleRecurrenceType' => $scheduleRecurrenceType,
'scheduleRecurrenceDetail' => $scheduleRecurrenceDetail,
'scheduleRecurrenceRange' => $scheduleRecurrenceRange
]);
} else {
# Create layout
$layout = $this->createLayout();
# Create new event with data from provideSuccessCasesCampaign where isCampaign is set to false
$response = $this->sendRequest('POST', $this->route, [
'fromDt' => Carbon::createFromTimestamp($scheduleFrom)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::createFromTimestamp($scheduleTo)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $layout->campaignId,
'displayGroupIds' => [$displayGroup->displayGroupId],
'displayOrder' => $scheduleOrder,
'isPriority' => $scheduleIsPriority,
'scheduleRecurrenceType' => $scheduleRecurrenceType,
'scheduleRecurrenceDetail' => $scheduleRecurrenceDetail,
'scheduleRecurrenceRange' => $scheduleRecurrenceRange
]);
}
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Clean up
$displayGroup->delete();
if ($campaign != null)
$campaign->delete();
if ($layout != null)
$layout->delete();
}
/**
* Each array is a test run
* Format ($isCampaign, $scheduleFrom, $scheduleTo, $scheduledayPartId, $scheduleRecurrenceType, $scheduleRecurrenceDetail, $scheduleRecurrenceRange, $scheduleOrder, $scheduleIsPriority)
* @return array
*/
public function provideSuccessCasesCampaign()
{
# Sets of data used in testAddEventCampaign, first argument (isCampaign) controls if it's layout or campaign
return [
'Campaign no priority, no recurrence' => [true, time()+3600, time()+7200, 0, NULL, NULL, NULL, 0, 0],
'Layout no priority, no recurrence' => [false, time()+3600, time()+7200, 0, NULL, NULL, NULL, 0, 0]
];
}
/**
* @group add
* @dataProvider provideSuccessCasesCommand
*/
public function testAddEventCommand($scheduleFrom, $scheduledayPartId, $scheduleRecurrenceType, $scheduleRecurrenceDetail, $scheduleRecurrenceRange, $scheduleOrder, $scheduleIsPriority)
{
# Create command
$command = (new XiboCommand($this->getEntityProvider()))->create('phpunit command', 'phpunit command desc', 'code');
# Create Display Group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit group', 'phpunit description', 0, '');
# Create new event with scheduled command and data from provideSuccessCasesCommand
$response = $this->sendRequest('POST', $this->route, [
'fromDt' => Carbon::createFromTimestamp($scheduleFrom)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 2,
'commandId' => $command->commandId,
'displayGroupIds' => [$displayGroup->displayGroupId],
'displayOrder' => $scheduleOrder,
'isPriority' => $scheduleIsPriority,
'scheduleRecurrenceType' => $scheduleRecurrenceType,
'scheduleRecurrenceDetail' => $scheduleRecurrenceDetail,
'scheduleRecurrenceRange' => $scheduleRecurrenceRange
]);
# Check if successful
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Clean up
$displayGroup->delete();
$command->delete();
}
/**
* Each array is a test run
* Format ($scheduleFrom, $scheduleDisplays, $scheduledayPartId, $scheduleRecurrenceType, $scheduleRecurrenceDetail, $scheduleRecurrenceRange, $scheduleOrder, $scheduleIsPriority)
* @return array
*/
public function provideSuccessCasesCommand()
{
return [
'Command no priority, no recurrence' => [time()+3600, 0, NULL, NULL, NULL, 0, 0],
];
}
/**
* @group add
* @dataProvider provideSuccessCasesOverlay
*/
public function testAddEventOverlay($scheduleFrom, $scheduleTo, $scheduleCampaignId, $scheduleDisplays, $scheduledayPartId, $scheduleRecurrenceType, $scheduleRecurrenceDetail, $scheduleRecurrenceRange, $scheduleOrder, $scheduleIsPriority)
{
# Create new dispay group
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit group', 'phpunit description', 0, '');
# Create layout
$layout = $this->createLayout();
# Create new event with data from provideSuccessCasesOverlay
$response = $this->sendRequest('POST', $this->route, [
'fromDt' => Carbon::createFromTimestamp($scheduleFrom)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::createFromTimestamp($scheduleTo)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 3,
'campaignId' => $layout->campaignId,
'displayGroupIds' => [$displayGroup->displayGroupId],
'displayOrder' => $scheduleOrder,
'isPriority' => $scheduleIsPriority,
'scheduleRecurrenceType' => $scheduleRecurrenceType,
'scheduleRecurrenceDetail' => $scheduleRecurrenceDetail,
'scheduleRecurrenceRange' => $scheduleRecurrenceRange
]);
# Check if call was successful
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Clean up
$displayGroup->delete();
if ($layout != null)
$layout->delete();
}
/**
* Each array is a test run
* Format ($scheduleFrom, $scheduleTo, $scheduledayPartId, $scheduleRecurrenceType, $scheduleRecurrenceDetail, $scheduleRecurrenceRange, $scheduleOrder, $scheduleIsPriority)
* @return array
*/
public function provideSuccessCasesOverlay()
{
return [
'Overlay, no recurrence' => [time()+3600, time()+7200, 0, NULL, NULL, NULL, 0, 0, 0, 0],
];
}
/**
* @group minimal
*/
public function testEdit()
{
// Get a Display Group Id
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit group', 'phpunit description', 0, '');
// Create Campaign
/* @var XiboCampaign $campaign */
$campaign = (new XiboCampaign($this->getEntityProvider()))->create('phpunit');
# Create new event
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$campaign->campaignId,
[$displayGroup->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
$fromDt = time() + 3600;
$toDt = time() + 86400;
# Edit event
$response = $this->sendRequest('PUT',$this->route . '/' . $event->eventId, [
'fromDt' => Carbon::createFromTimestamp($fromDt)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::createFromTimestamp($toDt)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $event->campaignId,
'displayGroupIds' => [$displayGroup->displayGroupId],
'displayOrder' => 1,
'isPriority' => 1
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), "Not successful: " . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if edit was successful
$this->assertSame($toDt, intval($object->data->toDt));
$this->assertSame($fromDt, intval($object->data->fromDt));
# Tidy up
$displayGroup->delete();
$campaign->delete();
}
/**
* @param $eventId
*/
public function testDelete()
{
# Get a Display Group Id
$displayGroup = (new XiboDisplayGroup($this->getEntityProvider()))->create('phpunit group', 'phpunit description', 0, '');
# Create Campaign
/* @var XiboCampaign $campaign */
$campaign = (new XiboCampaign($this->getEntityProvider()))->create('phpunit');
# Create event
$event = (new XiboSchedule($this->getEntityProvider()))->createEventLayout(
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
Carbon::now()->addSeconds(7200)->format(DateFormatHelper::getSystemFormat()),
$campaign->campaignId,
[$displayGroup->displayGroupId],
0,
NULL,
NULL,
NULL,
0,
0,
0
);
# Delete event
$response = $this->sendRequest('DELETE',$this->route . '/' . $event->eventId);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
# Clean up
$displayGroup->delete();
$campaign->delete();
}
}

View File

@@ -0,0 +1,311 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Carbon\Carbon;
use Xibo\Entity\Display;
use Xibo\Helper\DateFormatHelper;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ScheduleTimezoneTest
* @package Xibo\Tests\integration
*/
class ScheduleTimezoneBaseCase extends LocalWebTestCase
{
use LayoutHelperTrait;
use DisplayHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $layout;
/** @var \Xibo\OAuth2\Client\Entity\XiboDisplay */
protected $display;
/** @var \Xibo\OAuth2\Client\Entity\XiboSchedule */
protected $event;
protected $timeZone = 'Asia/Hong_Kong';
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) .' Test');
// Create a Layout
$this->layout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->layout);
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
// Check us in again
$this->layout = $this->publish($this->layout);
// Build the layout
$this->buildLayout($this->layout);
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetTimezone($this->display, $this->timeZone);
$this->displaySetStatus($this->display, Display::$STATUS_DONE);
$this->displaySetLicensed($this->display);
// Make sure the Layout Status is as we expect
$this->assertTrue($this->layoutStatusEquals($this->layout, 1), 'Layout Status isnt as expected');
// Make sure our Display is already DONE
$this->assertTrue($this->displayStatusEquals($this->display, Display::$STATUS_DONE), 'Display Status isnt as expected');
// Check our timzone is set correctly
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->RegisterDisplay($this->display->license, $this->timeZone));
$this->assertEquals($this->timeZone, $xml->documentElement->getAttribute('localTimezone'), 'Timezone not correct');
$xml = null;
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
}
// </editor-fold>
public function testSchedule()
{
// Our CMS is in GMT
// Create a schedule one hours time in my player timezone
$localNow = Carbon::now()->setTimezone($this->timeZone);
$date = $localNow->copy()->addHour()->startOfHour();
$this->getLogger()->debug('Event start will be at: ' . $date->format(DateFormatHelper::getSystemFormat()));
$response = $this->sendRequest('POST','/schedule', [
'fromDt' => $date->format(DateFormatHelper::getSystemFormat()),
'toDt' => $date->copy()->addMinutes(30)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $this->layout->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 0,
'scheduleRecurrenceType' => null,
'scheduleRecurrenceDetail' => null,
'scheduleRecurrenceRange' => null,
'syncTimezone' => 0
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->Schedule($this->display->license));
//$this->getLogger()->debug($xml->saveXML());
// Check the filter from and to dates are correct
$this->assertEquals($localNow->startOfHour()->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterFrom'), 'Filter from date incorrect');
$this->assertEquals($localNow->addDays(2)->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterTo'), 'Filter to date incorrect');
// Check our event is present.
$layouts = $xml->getElementsByTagName('layout');
$this->assertTrue(count($layouts) == 1, 'Unexpected number of events');
foreach ($layouts as $layout) {
$xmlFromDt = $layout->getAttribute('fromdt');
$this->assertEquals($date->format(DateFormatHelper::getSystemFormat()), $xmlFromDt, 'From date doesnt match: ' . $xmlFromDt);
}
}
public function testRecurringSchedule()
{
// Our CMS is in GMT
// Create a schedule one hours time in my player timezone
// we start this schedule the day before
$localNow = Carbon::now()->setTimezone($this->timeZone);
$date = $localNow->copy()->subDay()->addHour()->startOfHour();
$this->getLogger()->debug('Event start will be at: ' . $date->format(DateFormatHelper::getSystemFormat()));
$response = $this->sendRequest('POST','/schedule', [
'fromDt' => $date->format(DateFormatHelper::getSystemFormat()),
'toDt' => $date->copy()->addMinutes(30)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $this->layout->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 0,
'recurrenceType' => 'Day',
'recurrenceDetail' => 1,
'recurrenceRange' => null,
'syncTimezone' => 0
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->Schedule($this->display->license));
//$this->getLogger()->debug($xml->saveXML());
// Check the filter from and to dates are correct
$this->assertEquals($localNow->startOfHour()->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterFrom'), 'Filter from date incorrect');
$this->assertEquals($localNow->addDays(2)->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterTo'), 'Filter to date incorrect');
// Check our event is present.
$layouts = $xml->getElementsByTagName('layout');
foreach ($layouts as $layout) {
// Move our day on (we know we're recurring by day), and that we started a day behind
$date->addDay();
$xmlFromDt = $layout->getAttribute('fromdt');
$this->assertEquals($date->format(DateFormatHelper::getSystemFormat()), $xmlFromDt, 'From date doesnt match: ' . $xmlFromDt);
}
}
public function testSyncedSchedule()
{
// Our CMS is in GMT
// Create a schedule one hours time in my CMS timezone
$localNow = Carbon::now()->setTimezone($this->timeZone);
// If this was 8AM local CMS time, we would expect the resulting date/times in the XML to have the equivilent
// timezone specific date/times
$date = Carbon::now()->copy()->addHour()->startOfHour();
$localDate = $date->copy()->timezone($this->timeZone);
$this->getLogger()->debug('Event start will be at: ' . $date->format(DateFormatHelper::getSystemFormat()) . ' which is ' . $localDate->format(DateFormatHelper::getSystemFormat()) . ' local time.');
$response = $this->sendRequest('POST','/schedule', [
'fromDt' => $date->format(DateFormatHelper::getSystemFormat()),
'toDt' => $date->copy()->addMinutes(30)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $this->layout->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 0,
'recurrenceType' => null,
'recurrenceDetail' => null,
'recurrenceRange' => null,
'syncTimezone' => 1
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->Schedule($this->display->license));
//$this->getLogger()->debug($xml->saveXML());
// Check the filter from and to dates are correct
$this->assertEquals($localNow->startOfHour()->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterFrom'), 'Filter from date incorrect');
$this->assertEquals($localNow->addDays(2)->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterTo'), 'Filter to date incorrect');
// Check our event is present.
$layouts = $xml->getElementsByTagName('layout');
foreach ($layouts as $layout) {
$xmlFromDt = $layout->getAttribute('fromdt');
$this->assertEquals($localDate->format(DateFormatHelper::getSystemFormat()), $xmlFromDt, 'From date doesnt match: ' . $xmlFromDt);
}
}
public function testSyncedRecurringSchedule()
{
// Our CMS is in GMT
// Create a schedule one hours time in my CMS timezone
$localNow = Carbon::now()->setTimezone($this->timeZone);
// If this was 8AM local CMS time, we would expect the resulting date/times in the XML to have the equivilent
// timezone specific date/times
$date = Carbon::now()->copy()->subDay()->addHour()->startOfHour();
$localDate = $date->copy()->timezone($this->timeZone);
$this->getLogger()->debug('Event start will be at: ' . $date->format(DateFormatHelper::getSystemFormat()) . ' which is ' . $localDate->format(DateFormatHelper::getSystemFormat()) . ' local time.');
$response = $this->sendRequest('POST','/schedule', [
'fromDt' => $date->format(DateFormatHelper::getSystemFormat()),
'toDt' => $date->copy()->addMinutes(30)->format(DateFormatHelper::getSystemFormat()),
'eventTypeId' => 1,
'campaignId' => $this->layout->campaignId,
'displayGroupIds' => [$this->display->displayGroupId],
'displayOrder' => 1,
'isPriority' => 0,
'recurrenceType' => 'Day',
'recurrenceDetail' => 1,
'recurrenceRange' => null,
'syncTimezone' => 1
]);
$this->assertSame(200, $response->getStatusCode(), 'Not successful: ' . $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
$xml = new \DOMDocument();
$xml->loadXML($this->getXmdsWrapper()->Schedule($this->display->license));
//$this->getLogger()->debug($xml->saveXML());
// Check the filter from and to dates are correct
$this->assertEquals($localNow->startOfHour()->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterFrom'), 'Filter from date incorrect');
$this->assertEquals($localNow->addDays(2)->format(DateFormatHelper::getSystemFormat()), $xml->documentElement->getAttribute('filterTo'), 'Filter to date incorrect');
// Check our event is present.
$layouts = $xml->getElementsByTagName('layout');
foreach ($layouts as $layout) {
// Move our day on (we know we're recurring by day), and that we started a day behind
// we use addRealDay because our synced calendar entry _should_ change its time over a DST switch
$localDate->addRealDay();
$xmlFromDt = $layout->getAttribute('fromdt');
$this->assertEquals($localDate->format(DateFormatHelper::getSystemFormat()), $xmlFromDt, 'From date doesnt match: ' . $xmlFromDt);
}
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Copyright (C) 2019 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
class ScheduleTimezoneHongKongTest extends ScheduleTimezoneBaseCase
{
protected $timeZone = 'Asia/Hong_Kong';
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Copyright (C) 2019 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
class ScheduleTimezoneLATest extends ScheduleTimezoneBaseCase
{
protected $timeZone = 'America/Los_Angeles';
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Copyright (C) 2019 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
class ScheduleTimezoneLondonTest extends ScheduleTimezoneBaseCase
{
protected $timeZone = 'Europe/London';
}

View File

@@ -0,0 +1,232 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class SearchFilterTest
* @package Xibo\Tests\integration
*/
class SearchFilterTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboLayout */
protected $layout2;
/** @var XiboLayout */
protected $layout3;
/** @var XiboLayout */
protected $layout4;
/** @var XiboLayout */
protected $layout5;
/** @var XiboLayout */
protected $layout6;
// <editor-fold desc="Init">
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup test for ' . get_class($this) . ' Test');
// Create 6 layouts to test with
$this->layout = (new XiboLayout($this->getEntityProvider()))
->create(
'integration layout 1',
'PHPUnit Created Layout for Automated Integration Testing',
'',
$this->getResolutionId('landscape')
);
$this->layout2 = (new XiboLayout($this->getEntityProvider()))
->create(
'integration example layout 2',
'PHPUnit Created Layout for Automated Integration Testing',
'',
$this->getResolutionId('landscape')
);
$this->layout3 = (new XiboLayout($this->getEntityProvider()))
->create(
'integration layout 3',
'PHPUnit Created Layout for Automated Integration Testing',
'',
$this->getResolutionId('landscape')
);
$this->layout4 = (new XiboLayout($this->getEntityProvider()))
->create(
'integration example 4',
'PHPUnit Created Layout for Automated Integration Testing',
'',
$this->getResolutionId('landscape')
);
$this->layout5 = (new XiboLayout($this->getEntityProvider()))
->create(
'example layout 5',
'PHPUnit Created Layout for Automated Integration Testing',
'',
$this->getResolutionId('landscape')
);
$this->layout6 = (new XiboLayout($this->getEntityProvider()))
->create(
'display different name',
'PHPUnit Created Layout for Automated Integration Testing',
'',
$this->getResolutionId('landscape')
);
$this->getLogger()->debug('Finished Setup');
}
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
$this->deleteLayout($this->layout);
$this->deleteLayout($this->layout2);
$this->deleteLayout($this->layout3);
$this->deleteLayout($this->layout4);
$this->deleteLayout($this->layout5);
$this->deleteLayout($this->layout6);
parent::tearDown();
}
// </editor-fold>
/**
* Search filter test.
*
* Single keyword
*/
public function testSearch()
{
$response = $this->sendRequest('GET', '/layout', ['layout' => 'integration']);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame(4, $object->data->recordsFiltered);
}
/**
* Search filter test
*
* Comma separated
*/
public function testSearchCommaSeparated()
{
$response = $this->sendRequest('GET', '/layout', ['layout' => 'integration,example']);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame(5, $object->data->recordsFiltered);
}
/**
* Search filter test
*
* Comma separated with not RLIKE filter
*/
public function testSearchCommaSeparatedWithNotRlike()
{
$response = $this->sendRequest('GET', '/layout', ['layout' => 'integration layout, -example']);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame(4, $object->data->recordsFiltered);
}
/**
* Search filter test
*
* Comma separated with not RLIKE filter
*/
public function testSearchCommaSeparatedWithNotRlike2()
{
$response = $this->sendRequest('GET', '/layout', ['layout' => 'example, -layout']);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame(4, $object->data->recordsFiltered);
}
/**
* Search filter test.
*
* partial match filter
*/
public function testSearchPartialMatch()
{
$response = $this->sendRequest('GET', '/layout', ['layout' => 'inte, exa']);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame(5, $object->data->recordsFiltered);
}
/**
* Search filter test.
*
* using regexp
*/
public function testSearchWithRegEx()
{
$response = $this->sendRequest('GET', '/layout', ['layout' => 'name$', 'useRegexForName' => 1]);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame(1, $object->data->recordsFiltered);
}
/**
* Search filter test.
*
* using regexp
*/
public function testSearchWithRegEx2()
{
$response = $this->sendRequest('GET', '/layout', ['layout' => '^example, ^disp', 'useRegexForName' => 1]);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertSame(2, $object->data->recordsFiltered);
}
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class StatisticsEventTest
* @package Xibo\Tests\Integration
*/
class StatisticsEventTest extends LocalWebTestCase
{
use LayoutHelperTrait, DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// Create a Layout
$this->layout = $this->createLayout();
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete stat records
self::$container->get('timeSeriesStore')
->deleteStats(Carbon::now(), Carbon::now()->startOfDay()->subDays(10));
}
/**
* Check if proof of play statistics are correct
*/
public function testProof()
{
$type = 'event';
// Checkout layout
$layout = $this->getDraft($this->layout);
$hardwareId = $this->display->license;
// One word name for the event
$eventName = Random::generateString(10, 'event');
// First insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="0"
tag="'.$eventName.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Second insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(1)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="0"
tag="'.$eventName.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Third insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(1)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="0"
tag="'.$eventName.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Get stats and see if they match with what we expect
$response = $this->sendRequest('GET', '/stats', [
'fromDt' => Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()),
'displayId' => $this->display->displayId,
'type' => $type
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertEquals(3, $object->data->recordsTotal);
$this->assertCount(3, $object->data->data);
}
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class StatisticsLayoutTest
* @package Xibo\Tests\Integration
*/
class StatisticsLayoutTest extends LocalWebTestCase
{
use LayoutHelperTrait, DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// Create a Layout
$this->layout = $this->createLayout();
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetLicensed($this->display);
$this->getLogger()->debug('Finished Setup');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete stat records
self::$container->get('timeSeriesStore')
->deleteStats(Carbon::now(), Carbon::now()->startOfDay()->subDays(10));
}
/**
* Check if proof of play statistics are correct
*/
public function testProof()
{
$type = 'layout';
$hardwareId = $this->display->license;
// Set start and date time
//
// $fromDt = '2018-02-12 00:00:00';
// $toDt = '2018-02-15 00:00:00';
//
// $fromDt2 = '2018-02-15 00:00:00';
// $toDt2 = '2018-02-16 00:00:00';
//
// $fromDt3 = '2018-02-16 00:00:00';
// $toDt3 = '2018-02-17 00:00:00';
// Add stats to the DB - known set
//
// 1 layout
// type,start,end,layout,media
// layout,2016-02-12 00:00:00, 2016-02-15 00:00:00, L1, NULL
//
// Result
// L1 72 hours
//
// 1 layout
// type,start,end,layout,media
// layout,2016-02-15 00:00:00, 2016-02-16 00:00:00, L1, NULL
//
// Result
// L1 24 hours
//
// 1 layout
// type,start,end,layout,media
// layout,2016-02-16 00:00:00, 2016-02-17 00:00:00, L1, NULL
//
// Result
// L1 24 hours
// First insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'" />
</stats>'
);
$this->assertSame(true, $response);
// Second insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(1)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Third insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(1)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Get stats and see if they match with what we expect
$response = $this->sendRequest('GET', '/stats', [
'fromDt' => Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()),
'displayId' => $this->display->displayId,
'layoutId' => [$this->layout->layoutId],
'type' => $type
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertEquals(3, $object->data->recordsTotal);
$this->assertCount(3, $object->data->data);
}
}

View File

@@ -0,0 +1,214 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class StatisticsMediaTest
* @package Xibo\Tests\Integration
*/
class StatisticsMediaTest extends LocalWebTestCase
{
use LayoutHelperTrait, DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $media2;
/** @var \Xibo\OAuth2\Client\Entity\XiboWidget */
private $widget;
/** @var \Xibo\OAuth2\Client\Entity\XiboWidget */
private $widget2;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// Create a Layout
$this->layout = $this->createLayout();
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetLicensed($this->display);
// Upload some media
$this->media = (new XiboLibrary($this->getEntityProvider()))
->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/xts-night-001.jpg');
$this->media2 = (new XiboLibrary($this->getEntityProvider()))
->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/xts-layout-003-background.jpg');
// Checkout our Layout and add some Widgets to it.
$layout = $this->getDraft($this->layout);
// Add another region
// Assign media to the layouts default region.
$playlist = (new XiboPlaylist($this->getEntityProvider()))->assign([$this->media->mediaId, $this->media2->mediaId], 10, $layout->regions[0]->regionPlaylist->playlistId);
// Get Widget Ids
$this->widget = $playlist->widgets[0];
$this->widget2 = $playlist->widgets[1];
// Publish the Layout
$this->layout = $this->publish($this->layout);
$this->getLogger()->debug('Finished Setup');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the media records
$this->media->deleteAssigned();
$this->media2->deleteAssigned();
// Delete stat records
self::$container->get('timeSeriesStore')
->deleteStats(Carbon::now(), Carbon::now()->startOfDay()->subDays(10));
}
/**
* Check if proof of play statistics are correct
*/
public function testProof()
{
$type = 'media';
$hardwareId = $this->display->license;
// Set start and date time
//
// $fromDt = '2018-02-12 00:00:00';
// $toDt = '2018-02-17 00:00:00';
// Add stats to the DB - known set
//
// 1 layout, 2 region, 2 medias (1 per region)
// type,start,end,layout,media
// media,2018-02-12 00:00:00, 2018-02-13 00:00:00, L1, M1
// media,2018-02-13 00:00:00, 2018-02-14 00:00:00, L1, M1
// media,2018-02-16 00:00:00, 2018-02-17 12:00:00, L1, M1
// media,2018-02-14 00:00:00, 2018-02-15 00:00:00, L1, M2
// media,2018-02-15 00:00:00, 2018-02-16 00:00:00, L1, M2
// media,2018-02-16 00:00:00, 2018-02-16 12:00:00, L1, M2
//
// Result
// M1 60 hours
// M2 60 hours
// Insert all stats in one call to SubmitStats
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(4)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget->widgetId.'"/>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(4)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(3)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget->widgetId.'"/>
<stat fromdt="'. Carbon::now()->startOfDay()->subHours(12)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget->widgetId.'"/>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(3)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget2->widgetId.'"/>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(1)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget2->widgetId.'"/>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(1)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subHours(12)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget2->widgetId.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Get stats and see if they match with what we expect
$response = $this->sendRequest('GET', '/stats', [
'fromDt' => Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()),
'displayId' => $this->display->displayId,
'layoutId' => [$this->layout->layoutId],
'type' => $type
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertEquals(6, $object->data->recordsTotal);
$this->assertCount(6, $object->data->data);
}
}

View File

@@ -0,0 +1,246 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\OAuth2\Client\Entity\XiboStats;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class StatisticsTest
* @package Xibo\Tests\Integration
*/
class StatisticsTest extends LocalWebTestCase
{
use LayoutHelperTrait, DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $media2;
/** @var \Xibo\OAuth2\Client\Entity\XiboWidget */
private $widget;
/** @var \Xibo\OAuth2\Client\Entity\XiboWidget */
private $widget2;
/** @var \Xibo\OAuth2\Client\Entity\XiboWidget */
private $textWidget;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// Create a Layout
$this->layout = $this->createLayout();
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetLicensed($this->display);
// Upload some media
$this->media = (new XiboLibrary($this->getEntityProvider()))
->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/xts-night-001.jpg');
$this->media2 = (new XiboLibrary($this->getEntityProvider()))
->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/xts-layout-003-background.jpg');
// Checkout our Layout and add some Widgets to it.
$layout = $this->getDraft($this->layout);
// Create and assign new text widget
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$this->textWidget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Add another region
// Assign media to the layouts default region.
$playlist = (new XiboPlaylist($this->getEntityProvider()))->assign([$this->media->mediaId, $this->media2->mediaId], 10, $layout->regions[0]->regionPlaylist->playlistId);
// Get Widget Ids
$this->widget = $playlist->widgets[0];
$this->widget2 = $playlist->widgets[1];
// Publish the Layout
$this->layout = $this->publish($this->layout);
$this->getLogger()->debug('Finished Setup');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete the media records
$this->media->deleteAssigned();
$this->media2->deleteAssigned();
// Delete stat records
self::$container->get('timeSeriesStore')
->deleteStats(Carbon::now(), Carbon::now()->startOfDay()->subDays(10));
}
/**
* Test the method call with default values
*/
public function testListAll()
{
$this->getXmdsWrapper()->SubmitStats($this->display->license, '
<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subHours(12)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()) .'"
type="layout"
scheduleid="0"
layoutid="' . $this->layout->layoutId . '" />
</stats>');
$response = $this->sendRequest('GET', '/stats');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertEquals(1, $object->data->recordsTotal);
$this->assertCount(1, $object->data->data);
}
/**
* Check if proof of play statistics can be exported
*/
public function testExport()
{
$this->getXmdsWrapper()->SubmitStats($this->display->license, '
<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(4)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(3)->format(DateFormatHelper::getSystemFormat()) .'"
type="layout"
scheduleid="0"
layoutid="' . $this->layout->layoutId . '" />
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(4)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(3)->format(DateFormatHelper::getSystemFormat()) .'"
type="media"
scheduleid="0"
layoutid="' . $this->layout->layoutId . '"
mediaid="' . $this->widget->widgetId . '"/>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(4)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(3)->format(DateFormatHelper::getSystemFormat()) .'"
type="widget"
scheduleid="0"
layoutid="' . $this->layout->layoutId . '"
mediaid="' . $this->textWidget->widgetId . '"/>
</stats>');
$response = $this->sendRequest('GET', '/stats/export', [
'fromDt' => Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat())
]);
// Check 200
$this->assertSame(200, $response->getStatusCode());
// We're expecting a send file header as we're testing within Docker
$this->assertTrue($response->hasHeader('X-Sendfile'));
$this->assertSame('text/csv', $response->getHeader('Content-Type')[0] ?? '');
$this->assertGreaterThan(0, $response->getHeader('Content-Length')[0] ?? 0);
// We can't test the body, because there isn't any web server involved with this request.
}
public function testProofOldStats()
{
$hardwareId = $this->display->license;
// Attempt to insert stat data older than 30 days
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(35)->format('Y-m-d H:i:s') . '"
todt="'.Carbon::now()->startOfDay()->subDays(31)->format('Y-m-d H:i:s') .'"
type="layout"
scheduleid="0"
layoutid="' . $this->layout->layoutId . '" />
</stats>'
);
$this->assertSame(true, $response);
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(40)->format('Y-m-d H:i:s') . '"
todt="'.Carbon::now()->startOfDay()->subDays(38)->format('Y-m-d H:i:s') .'"
type="layout"
scheduleid="0"
layoutid="' . $this->layout->layoutId . '" />
</stats>'
);
$this->assertSame(true, $response);
// Default max stat age is 30 days, therefore we expect to get no results after the attempted inserts.
$stats = (new XiboStats($this->getEntityProvider()))->get([
'fromDt' => Carbon::now()->startOfDay()->subDays(41)->format('Y-m-d H:i:s'),
'toDt' => Carbon::now()->startOfDay()->subDays(31)->format('Y-m-d H:i:s'),
'layoutId' => [$this->layout->layoutId],
'type' => 'layout'
]);
$this->assertEquals(0, count($stats));
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Carbon\Carbon;
use Xibo\Helper\DateFormatHelper;
use Xibo\OAuth2\Client\Entity\XiboDisplay;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\OAuth2\Client\Entity\XiboText;
use Xibo\Tests\Helper\DisplayHelperTrait;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class StatisticsWidgetTest
* @package Xibo\Tests\Integration
*/
class StatisticsWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait, DisplayHelperTrait;
/** @var XiboLayout */
protected $layout;
/** @var XiboDisplay */
protected $display;
/** @var \Xibo\OAuth2\Client\Entity\XiboWidget */
private $widget;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
// Create a Layout
$this->layout = $this->createLayout();
// Create a Display
$this->display = $this->createDisplay();
$this->displaySetLicensed($this->display);
// Checkout our Layout and add some Widgets to it.
$layout = $this->getDraft($this->layout);
// Create and assign new text widget
$response = $this->getEntityProvider()->post('/playlist/widget/text/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'text' => 'Widget A',
'duration' => 100,
'useDuration' => 1
]);
$this->widget = (new XiboText($this->getEntityProvider()))->hydrate($response);
// Publish the Layout
$this->layout = $this->publish($this->layout);
$this->getLogger()->debug('Finished Setup');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
$this->getLogger()->debug('Tear Down');
parent::tearDown();
// Delete the Layout we've been working with
$this->deleteLayout($this->layout);
// Delete the Display
$this->deleteDisplay($this->display);
// Delete stat records
self::$container->get('timeSeriesStore')->deleteStats(Carbon::now(), Carbon::now()->startOfDay()->subDays(10));
}
/**
* Check if proof of play statistics are correct
*/
public function testProof()
{
$type = 'widget';
$hardwareId = $this->display->license;
// First insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget->widgetId.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Second insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(2)->format(DateFormatHelper::getSystemFormat()) . '"
todt="'.Carbon::now()->startOfDay()->subDays(1)->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget->widgetId.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Third insert
$response = $this->getXmdsWrapper()->SubmitStats(
$hardwareId,
'<stats>
<stat fromdt="'. Carbon::now()->startOfDay()->subDays(1)->format(DateFormatHelper::getSystemFormat()) .'"
todt="'. Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()) .'"
type="'.$type.'"
scheduleid="0"
layoutid="'.$this->layout->layoutId.'"
mediaid="'.$this->widget->widgetId.'"/>
</stats>'
);
$this->assertSame(true, $response);
// Get stats and see if they match with what we expect
$response = $this->sendRequest('GET', '/stats', [
'fromDt' => Carbon::now()->startOfDay()->subDays(5)->format(DateFormatHelper::getSystemFormat()),
'toDt' => Carbon::now()->startOfDay()->format(DateFormatHelper::getSystemFormat()),
'displayId' => $this->display->displayId,
'layoutId' => [$this->layout->layoutId],
'type' => $type
]);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertEquals(3, $object->data->recordsTotal);
$this->assertCount(3, $object->data->data);
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* Xibo - Digital Signage - http://www.xibo.org.uk
* Copyright (C) 2015-2018 Spring Signage Ltd
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboLayout;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class TemplateTest
* @package Xibo\Tests
*/
class TemplateTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/**
* Show Templates
*/
public function testListAll()
{
$response = $this->sendRequest('GET','/template');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
}
/**
* Add Template
*/
public function testAdd()
{
# Create random name and new layout
$layout = $this->createLayout();
# Generate second random name
$name2 = Random::generateString(8, 'phpunit');
# Create template using our layout and new name
$response = $this->sendRequest('POST','/template/' . $layout->layoutId, [
'name' => $name2,
'includeWidgets' => 1,
'tags' => 'phpunit',
'description' => $layout->description
]);
# Check if successful
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertObjectHasAttribute('id', $object);
# Check if it has edited name
$this->assertSame($name2, $object->data->layout);
// Expect 2 tags phpunit added in this request and template tag.
$this->assertSame(2,count($object->data->tags));
$templateId = $object->id;
# delete template as we no longer need it
$template = (new XiboLayout($this->getEntityProvider()))->getByTemplateId($templateId);
$template->delete();
# delete layout as we no longer need it
$layout->delete();
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* Copyright (C) 2021 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Exception\XiboApiException;
use Xibo\Tests\LocalWebTestCase;
/**
* Class UserGroupTest
* @package Xibo\Tests\integration
*/
class UserGroupTest extends LocalWebTestCase
{
/**
* Add a new group and then check it was added correctly.
*/
public function testAdd()
{
$params = [
'group' => Random::generateString(),
'description' => Random::generateString()
];
$response = $this->sendRequest('POST', '/group', $params);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertObjectHasAttribute('id', $object, $response->getBody());
// Use the API to get it out again
try {
//$group = (new XiboUserGroup($this->getEntityProvider()))->getById($object->id);
$group = $this->getEntityProvider()->get('/group', ['userGroupId' => $object->id])[0];
// Check our key parts match.
$this->assertSame($params['group'], $group['group'], 'Name does not match');
$this->assertSame($params['description'], $group['description'], 'Description does not match');
} catch (XiboApiException $e) {
$this->fail('Group not found. e = ' . $e->getMessage());
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
/*
* Copyright (c) 2022 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboUser;
use Xibo\Tests\LocalWebTestCase;
/**
* Class UserTest
* @package Xibo\Tests
*/
class UserTest extends LocalWebTestCase
{
/**
* Show me
*/
public function testGetMe()
{
$response = $this->sendRequest('GET','/user/me');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
$this->assertSame('phpunit', $object->data->userName);
}
/**
* Show all users
*/
public function testGetUsers()
{
$response = $this->sendRequest('GET','/user');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object);
}
/**
* Add new user
*/
public function testAdd()
{
$group = $this->getEntityProvider()->get('/group', ['userGroup' => 'Users'])[0];
$userName = Random::generateString();
$response = $this->sendRequest('POST','/user', [
'userName' => $userName,
'userTypeId' => 3,
'homePageId' => 'icondashboard.view',
'homeFolderId' => 1,
'password' => 'newUserPassword',
'groupId' => $group['groupId'],
'libraryQuota' => 0
]);
$this->assertSame(200, $response->getStatusCode(), $response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->assertObjectHasAttribute('id', $object, $response->getBody());
$this->assertSame($userName, $object->data->userName);
$this->assertSame(3, $object->data->userTypeId);
$this->assertSame('icondashboard.view', $object->data->homePageId);
$userCheck = (new XiboUser($this->getEntityProvider()))->getById($object->id);
$userCheck->delete();
}
public function testAddEmptyPassword()
{
$group = $this->getEntityProvider()->get('/group', ['userGroup' => 'Users'])[0];
$response = $this->sendRequest('POST', '/user', [
'userName' => Random::generateString(),
'userTypeId' => 3,
'homePageId' => 'icondashboard.view',
'homeFolderId' => 1,
'password' => null,
'groupId' => $group['groupId'],
'libraryQuota' => 0
]);
$this->assertSame(422, $response->getStatusCode(), $response->getBody());
}
}

View File

@@ -0,0 +1,182 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboImage;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class AudioWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class AudioWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var XiboLibrary */
protected $media;
/** @var XiboLibrary */
protected $audio;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create some media to upload
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/cc0_f1_gp_cars_pass_crash.mp3');
$this->audio = (new XiboLibrary($this->getEntityProvider()))->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/cc0_f1_gp_cars_pass_crash.mp3');
// Assign the media we've created to our regions playlist.
$playlist = (new XiboPlaylist($this->getEntityProvider()))->assign([$this->media->mediaId], 10, $layout->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
// Tidy up the media
$this->media->delete();
$this->audio->delete();
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* @throws \Xibo\OAuth2\Client\Exception\XiboApiException
*/
public function testEdit()
{
$this->getLogger()->debug('testEdit ' . get_class($this) .' Test');
// Now try to edit our assigned Media Item.
$name = 'Edited Name ' . Random::generateString(5);
$duration = 80;
$useDuration = 1;
$mute = 0;
$loop = 0;
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'name' => $name,
'duration' => $duration,
'useDuration' => $useDuration,
'mute' => $mute,
'loop' => $loop,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']
);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboImage $widgetOptions */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$widgetOptions = (new XiboImage($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($name, $widgetOptions->name);
$this->assertSame($duration, $widgetOptions->duration);
foreach ($widgetOptions->widgetOptions as $option) {
if ($option['option'] == 'mute') {
$this->assertSame($mute, intval($option['value']));
}
if ($option['option'] == 'loop') {
$this->assertSame($loop, intval($option['value']));
}
if ($option['option'] == 'useDuration') {
$this->assertSame($useDuration, intval($option['value']));
}
}
$this->getLogger()->debug('testEdit finished');
}
/**
* Test to edit and assign an auto item to a widget
*/
public function testAssign()
{
$this->getLogger()->debug('testAssign');
$volume = 80;
$loop = 1;
// Add audio to image assigned to a playlist
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId . '/audio', [
'mediaId' => $this->audio->mediaId,
'volume' => $volume,
'loop' => $loop,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']
);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
/** @var XiboImage $widgetOptions */
$widgetOptions = (new XiboImage($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($this->media->name, $widgetOptions->name);
$this->assertSame($this->media->mediaId, intval($widgetOptions->mediaIds[0]));
$this->assertSame($this->audio->mediaId, intval($widgetOptions->mediaIds[1]));
$this->assertSame($volume, intval($widgetOptions->audio[0]['volume']));
$this->assertSame($loop, intval($widgetOptions->audio[0]['loop']));
$this->getLogger()->debug('testAssign finished');
}
}

View File

@@ -0,0 +1,138 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\OAuth2\Client\Entity\XiboClock;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ClockWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class ClockWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/clock/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* Each array is a test run
* Format ($name, $duration, $useDuration, $theme, $clockTypeId, $offset, $format, $showSeconds, $clockFace)
* @return array
*/
public function provideSuccessCases()
{
# Sets of data used in testAdd
return [
'Analogue' => ['Api Analogue clock', 20, 1, 1, 1, null, null, 0, 'TwentyFourHourClock'],
'Digital' => ['API digital clock', 20, 1, 0, 2, null, '[HH:mm]', 0, 'TwentyFourHourClock'],
'Flip 24h' => ['API Flip clock 24h', 5, 1, 0, 3, null, null, 1, 'TwentyFourHourClock'],
'Flip counter' => ['API Flip clock Minute counter', 50, 1, 0, 3, null, null, 1, 'MinuteCounter']
];
}
/**
* @param $name
* @param $duration
* @param $useDuration
* @param $theme
* @param $clockTypeId
* @param $offset
* @param $format
* @param $showSeconds
* @param $clockFace
* @dataProvider provideSuccessCases
*/
public function testEdit($name, $duration, $useDuration, $theme, $clockTypeId, $offset, $format, $showSeconds, $clockFace)
{
$response = $this->sendRequest('PUT', '/playlist/widget/' . $this->widgetId, [
'name' => $name,
'useDuration' => $useDuration,
'duration' => $duration,
'themeId' => $theme,
'clockTypeId' => $clockTypeId,
'offset' => $offset,
'format' => $format,
'showSeconds' => $showSeconds,
'clockFace' => $clockFace
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboClock $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboClock($this->getEntityProvider()))->hydrate($response[0]);
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'clockTypeId') {
$this->assertSame($clockTypeId, intval($option['value']));
} else {
if ($option['option'] == 'name') {
$this->assertSame($name, $option['value']);
}
}
}
}
}

View File

@@ -0,0 +1,144 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\OAuth2\Client\Entity\XiboCurrencies;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class CurrenciesWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class CurrenciesWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/currencies/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* Each array is a test run
* Format ($overrideTemplate, $templateId, $name, $duration, $useDuration, $base, $items, $reverseConversion, $effect, $speed, $backgroundColor, $noRecordsMessage, $dateFormat, $updateInterval, $durationIsPerItem, $widgetOriginalWidth, $widgetOriginalHeight, $itemsPerPage, $mainTemplate, $itemTemplate, $styleSheet, $javaScript)
* @return array
*/
public function provideSuccessCases()
{
# Sets of data used in testAdd
return [
'No override template 1' => [false, 'currencies1', 'template 1', 6, 1, 'GBP', 'PLN', 0, NULL, NULL, NULL, 'No messages', NULL, 12, 1, null, null, 5, null, null, null, null],
'No override template 2 reverse' => [false, 'currencies2', 'template 2', 120, 1, 'GBP', 'EUR', 1, NULL, NULL, NULL, 'No messages', NULL, 120, 0, null, null, 2, null, null, null, null],
'Override' => [true, 'currencies1', 'override template', 12, 1, 'GBP', 'EUR', 0, NULL, NULL, NULL, 'No messages', NULL, 60, 1, 1000, 800, 5, '<div class="container-main"><div class="container "><div class="row row-header"><div class="col-2 offset-xs-7 text-center value">BUY</div><div class="col-2 offset-xs-1 value text-center">SELL</div></div><div id="cycle-container">[itemsTemplate]</div></div></div>', '<div class="row row-finance"><div class="col-1 flags"><img class="img-circle center-block " src="[CurrencyFlag]"></div><div class="col-1 value ">[NameShort]</div><div class="col-2 offset-xs-5 text-center value">[Bid]</div><div class="col-2 offset-xs-1 value text-center">[Ask]</div> </div>','body { font-family: "Helvetica", "Arial", sans-serif; line-height: 1; } .container-main {height: 420px !important;width: 820px !important; } .container { height: 420px !important; width: 820px !important; float: left; margin-top: 20px; } .row-finance { height: 60px; background: rgba(0, 0, 0, 0.87); margin-bottom: 20px; } .row {margin-right: 0; margin-left: 0; } .row-header { margin-right: -15px; margin-left: -15px; margin-bottom: 20px; } #cycle-container { margin-left: -15px; margin-right: -15px; } .value { font-size: 20px; padding-top: 20px; font-weight: bold; color: #fff; } .down-arrow { font-size: 20px; color: red; padding-top: 17px; } .up-arrow { font-size: 20px;color: green; padding-top: 17px; } .variant { font-size: 20px; padding-top: 17px; } .flags { padding-top: 4px; } .center-block { width: 50px; height: 50px; }', NULL]
];
}
/**
* This test works correctly, it's marked as broken because we don't have this widget installed by default
* @group broken
* @dataProvider provideSuccessCases
*/
public function testEdit($isOverride, $templateId, $name, $duration, $useDuration, $base, $items, $reverseConversion, $effect, $speed, $backgroundColor, $noRecordsMessage, $dateFormat, $updateInterval, $durationIsPerItem, $widgetOriginalWidth, $widgetOriginalHeight, $itemsPerPage, $mainTemplate, $itemTemplate, $styleSheet, $javaScript)
{
# Edit currency widget and change name, duration, template, reverseConversion and items
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'templateId' => $templateId,
'name' => $name,
'duration' => $duration,
'useDuration' => $useDuration,
'base' => $base,
'items' => $items,
'reverseConversion' => $reverseConversion,
'effect' => $effect,
'speed' => $speed,
'backgroundColor' => $backgroundColor,
'noRecordsMessage' => $noRecordsMessage,
'dateFormat' => $dateFormat,
'updateInterval' => $updateInterval,
'durationIsPerItem' => $durationIsPerItem,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboCurrencies $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboCurrencies($this->getEntityProvider()))->hydrate($response[0]);
# check if changes were correctly saved
$this->assertSame($name, $checkWidget->name);
$this->assertSame($duration, $checkWidget->duration);
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'templateId') {
$this->assertSame($templateId, $option['value']);
}
if ($option['option'] == 'items') {
$this->assertSame($items, $option['value']);
}
if ($option['option'] == 'reverseConversion') {
$this->assertSame($reverseConversion, intval($option['value']));
}
}
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\integration\Widget;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDataSet;
use Xibo\OAuth2\Client\Entity\XiboTicker;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class DataSetTickerWidgetTest
* @package Xibo\Tests\integration\Widget
*/
class DataSetTickerWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
/** @var XiboDataSet */
protected $dataSet;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Add a DataSet
$this->dataSet = (new XiboDataSet($this->getEntityProvider()))->create(Random::generateString(), 'Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/datasetticker/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'step' => 1,
'dataSetId' => $this->dataSet->dataSetId
]);
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
// Delete the DataSet
$this->dataSet->deleteWData();
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* Edit dataSet ticker
*/
public function testEditDataset()
{
$this->getLogger()->debug('testEdit ' . get_class($this) .' Test');
// Edit ticker
$noDataMessage = 'no records found';
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'name' => 'Edited widget',
'duration' => 90,
'useDuration' => 1,
'updateInterval' => 100,
'effect' => 'fadeout',
'speed' => 500,
'template' => '[Col1]',
'durationIsPerItem' => 1,
'itemsSideBySide' => 1,
'upperLimit' => 0,
'lowerLimit' => 0,
'itemsPerPage' => 5,
'noDataMessage' => $noDataMessage
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode(), 'Incorrect status: ' . $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$this->getLogger()->debug('Request successful, double check contents.');
/** @var XiboTicker $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboTicker($this->getEntityProvider()))->hydrate($response[0]);
// check if changes were correctly saved
$this->assertSame('Edited widget', $checkWidget->name);
$this->assertSame(90, $checkWidget->duration);
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'updateInterval') {
$this->assertSame(100, intval($option['value']));
}
if ($option['option'] == 'effect') {
$this->assertSame('fadeout', $option['value']);
}
if ($option['option'] == 'speed') {
$this->assertSame(500, intval($option['value']));
}
if ($option['option'] == 'template') {
$this->assertSame('[Col1]', $option['value']);
}
if ($option['option'] == 'durationIsPerItem') {
$this->assertSame(1, intval($option['value']));
}
if ($option['option'] == 'itemsSideBySide') {
$this->assertSame(1, intval($option['value']));
}
if ($option['option'] == 'upperLimit') {
$this->assertSame(0, intval($option['value']));
}
if ($option['option'] == 'lowerLimit') {
$this->assertSame(0, intval($option['value']));
}
if ($option['option'] == 'itemsPerPage') {
$this->assertSame(5, intval($option['value']));
}
if ($option['option'] == 'noDataMessage') {
$this->assertSame($noDataMessage, $option['value']);
}
}
$this->getLogger()->debug('testEdit finished');
}
}

View File

@@ -0,0 +1,139 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboDataSet;
use Xibo\OAuth2\Client\Entity\XiboDataSetColumn;
use Xibo\OAuth2\Client\Entity\XiboDataSetView;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
class DataSetViewWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
/** @var XiboDataSet */
protected $dataSet;
/** @var XiboDataSetColumn */
protected $dataSetColumn;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Add a DataSet
$this->dataSet = (new XiboDataSet($this->getEntityProvider()))->create(Random::generateString(), 'Test');
// Create a Column for our DataSet
$this->dataSetColumn = (new XiboDataSetColumn($this->getEntityProvider()))->create($this->dataSet->dataSetId, Random::generateString(8, 'phpunit'),'', 2, 1, 1, '');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/datasetview/' . $layout->regions[0]->regionPlaylist->playlistId);
$response = $this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'step' => 1,
'dataSetId' => $this->dataSet->dataSetId
]);
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
// Delete the DataSet
$this->dataSet->deleteWData();
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
public function testEdit()
{
$nameNew = 'Edited Name';
$durationNew = 80;
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'dataSetColumnId' => [$this->dataSetColumn->dataSetColumnId],
'name' => $nameNew,
'duration' => $durationNew,
'updateInterval' => 100,
'rowsPerPage' => 2,
'showHeadings' => 0,
'upperLimit' => 0,
'lowerLimit' => 0,
'filter' => null,
'ordering' => null,
'templateId' => 'light-green',
'overrideTemplate' => 0,
'useOrderingClause' => 0,
'useFilteringClause' => 0,
'noDataMessage' => 'No Data returned',
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboDataSetView $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboDataSetView($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($nameNew, $checkWidget->name);
$this->assertSame($durationNew, $checkWidget->duration);
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'templateId') {
$this->assertSame('light-green', $option['value']);
} else if ($option['option'] == 'updateInterval') {
$this->assertSame(100, intval($option['value']));
} else if ($option['option'] == 'name') {
$this->assertSame($nameNew, $option['value']);
}
}
}
}

View File

@@ -0,0 +1,108 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboEmbedded;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class EmbeddedWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class EmbeddedWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/embedded/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* @throws \Xibo\OAuth2\Client\Exception\XiboApiException
*/
public function testEdit()
{
$name = Random::generateString();
$durationNew = 80;
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'name' => $name,
'duration' => $durationNew,
'transparency' => 1,
'scaleContent' => 1,
'embedHtml' => null,
'embedScript' => null,
'embedStyle' => '<style type="text/css"> </style>'
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboEmbedded $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboEmbedded($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($name, $checkWidget->name);
$this->assertSame($durationNew, $checkWidget->duration);
}
}

View File

@@ -0,0 +1,149 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\OAuth2\Client\Entity\XiboGoogleTraffic;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class GoogleTrafficWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class GoogleTrafficWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
parent::installModuleIfNecessary('googletraffic', '\Xibo\Widget\GoogleTraffic');
}
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/googletraffic/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* Each array is a test run
* Format ($name, $duration, $useDisplayLocation, $longitude, $latitude, $zoom)
* @return array
*/
public function provideEditCases()
{
# Sets of data used in testAdd
return [
'Use Display location' => [200, 'Traffic with display location', 2000, 1, null, null, 100],
'Custom location 1' => [200, 'Traffic with custom location - Italy', 4500, 0, 7.640974, 45.109612, 80],
'Custom location 2' => [200, 'Traffic with custom location - Japan', 4500, 0, 35.7105, 139.7336, 50],
'No zoom provided' => [422, 'no zoom', 2000, 1, null, null, null],
'no lat/long' => [422, 'no lat/long provided with useDisplayLocation 0', 3000, 0, null, null, 20],
'low min duration' => [422, 'Traffic with display location', 20, 1, null, null, 100],
];
}
/**
* Edit
* @dataProvider provideEditCases
* This test works correctly, it's marked as broken because we don't have this widget installed by default
* @group broken
*/
public function testEdit($statusCode, $name, $duration, $useDisplayLocation, $lat, $long, $zoom)
{
$this->getLogger()->debug('testEdit ' . get_class($this) .' Test');
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'name' => $name,
'duration' => $duration,
'useDuration' => 1,
'useDisplayLocation' => $useDisplayLocation,
'longitude' => $long,
'latitude' => $lat,
'zoom' => $zoom,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame($statusCode, $response->getStatusCode(), 'Incorrect status code.', var_export($response, true));
if ($statusCode == 422)
return;
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboGoogleTraffic $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboGoogleTraffic($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($name, $checkWidget->name);
$this->assertSame($duration, $checkWidget->duration);
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'longitude' && $long !== null) {
$this->assertSame($long, floatval($option['value']));
}
if ($option['option'] == 'latitude' && $lat !== null) {
$this->assertSame($lat, floatval($option['value']));
}
if ($option['option'] == 'zoom') {
$this->assertSame($zoom, intval($option['value']));
}
}
}
}

View File

@@ -0,0 +1,139 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\OAuth2\Client\Entity\XiboHls;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class HlsWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class HlsWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
parent::installModuleIfNecessary('hls', '\Xibo\Widget\Hls');
}
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/hls/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->widgetId = $response['widgetId'];
$this->getLogger()->debug('Setup Finished');
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* Each array is a test run
* Format ($name, $useDuration, $duration, $uri, $mute, $transparency)
* @return array
*/
public function provideEditCases()
{
# Sets of data used in testAdd
return [
'HLS stream' => [200, 'HLS stream', 1, 20, 'http://ceu.xibo.co.uk/hls/big_buck_bunny_adaptive_master.m3u8', 0, 0],
'HLS stream 512' => [200, 'HLS stream with transparency', 1, 20, 'http://ceu.xibo.co.uk/hls/big_buck_bunny_adaptive_512.m3u8', 0, 1],
'No url provided' => [422, 'no uri', 1, 10, '', 0, 0],
'No duration provided' => [422, 'no duration with useDuration 1', 1, 0, 'http://ceu.xibo.co.uk/hls/big_buck_bunny_adaptive_512.m3u8', 0, 0],
];
}
/**
* Edit
* @dataProvider provideEditCases
*/
public function testEdit($statusCode, $name, $useDuration, $duration, $uri, $mute, $transparency)
{
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'name' => $name,
'useDuration' => $useDuration,
'duration' => $duration,
'uri' => $uri,
'mute' => $mute,
'transparency' => $transparency,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame($statusCode, $response->getStatusCode());
if ($statusCode == 422)
return;
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboHls $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboHls($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($name, $checkWidget->name);
$this->assertSame($duration, $checkWidget->duration);
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'uri') {
$this->assertSame($uri, urldecode(($option['value'])));
}
}
}
}

View File

@@ -0,0 +1,135 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboImage;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ImageWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class ImageWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var XiboLibrary */
protected $media;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create some media to upload
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/xts-night-001.jpg');
// Assign the media we've created to our regions playlist.
$playlist = (new XiboPlaylist($this->getEntityProvider()))->assign([$this->media->mediaId], 10, $layout->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
// Tidy up the media
$this->media->delete();
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
public function testEdit()
{
$this->getLogger()->debug('testEdit ' . get_class($this) .' Test');
// Edit properties
$nameNew = 'Edited Name: ' . Random::generateString(5);
$durationNew = 80;
$scaleTypeIdNew = 'stretch';
$alignIdNew = 'center';
$valignIdNew = 'top';
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'name' => $nameNew,
'duration' => $durationNew,
'useDuration' => 1,
'scaleTypeId' => $scaleTypeIdNew,
'alignId' => $alignIdNew,
'valignId' => $valignIdNew,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboImage $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboImage($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($nameNew, $checkWidget->name);
$this->assertSame($durationNew, $checkWidget->duration);
$this->assertSame($this->media->mediaId, intval($checkWidget->mediaIds[0]));
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'scaleTypeId') {
$this->assertSame($scaleTypeIdNew, $option['value']);
}
if ($option['option'] == 'alignId') {
$this->assertSame($alignIdNew, $option['value']);
}
if ($option['option'] == 'valignId') {
$this->assertSame($valignIdNew, $option['value']);
}
}
}
}

View File

@@ -0,0 +1,128 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\OAuth2\Client\Entity\XiboLocalVideo;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LocalVideoWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class LocalVideoWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/localvideo/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* Each array is a test run
* Format ($name, $duration, $theme, $clockTypeId, $offset, $format, $showSeconds, $clockFace)
* @return array
*/
public function provideEditCases()
{
# Sets of data used in testAdd
return [
'Aspect' => ['rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov', 30, 1, 'aspect', 0],
'Stretch muted' => ['rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov', 100, 1, ' stretch', 1],
];
}
/**
* @throws \Xibo\OAuth2\Client\Exception\XiboApiException
* @dataProvider provideEditCases
*/
public function testEdit($uri, $duration, $useDuration, $scaleTypeId, $mute)
{
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'uri' => $uri,
'duration' => $duration,
'useDuration' => $useDuration,
'scaleTypeId' => $scaleTypeId,
'mute' => $mute,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboLocalVideo $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboLocalVideo($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($duration, $checkWidget->duration);
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'uri') {
$this->assertSame($uri, urldecode($option['value']));
}
if ($option['option'] == 'scaleTypeId') {
$this->assertSame($scaleTypeId, $option['value']);
}
if ($option['option'] == 'mute') {
$this->assertSame($mute, intval($option['value']));
}
}
}
}

View File

@@ -0,0 +1,137 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class LocalVideoWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class MenuBoardWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var int */
protected $widgetId;
private $menuBoard;
private $menuBoardCategory;
private $menuBoardProduct;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/menuboard/' . $layout->regions[0]->regionPlaylist->playlistId);
$this->menuBoard = $this->getEntityProvider()->post('/menuboard', [
'name' => 'phpunit Menu board',
'description' => 'Description for test Menu Board'
]);
$this->menuBoardCategory = $this->getEntityProvider()->post('/menuboard/' . $this->menuBoard['menuId'] . '/category', [
'name' => 'phpunit Menu Board Category'
]);
$this->menuBoardProduct = $this->getEntityProvider()->post('/menuboard/' . $this->menuBoardCategory['menuCategoryId'] . '/product', [
'name' => 'phpunit Menu Board Product',
'price' => '$11.11'
]);
$this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'step' => 1,
'menuId' => $this->menuBoard['menuId'],
'templateId' => 'menuboard1'
]);
$this->getEntityProvider()->put('/playlist/widget/' . $response['widgetId'], [
'step' => 2,
'menuBoardCategories_1' => [$this->menuBoardCategory['menuCategoryId']]
]);
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
if ($this->menuBoard['menuId'] !== null) {
$this->getEntityProvider()->delete('/menuboard/' . $this->menuBoard['menuId']);
}
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
public function testEdit()
{
$response = $this->sendRequest('PUT', '/playlist/widget/' . $this->widgetId, [
'name' => 'Test Menu Board Widget',
'duration' => 60,
'useDuration' => 1,
'showUnavailable' => 0,
'productsHighlight' => [$this->menuBoardProduct['menuProductId']]
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
$widget = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId])[0];
$this->assertSame(60, $widget['duration']);
foreach ($widget['widgetOptions'] as $option) {
if ($option['option'] == 'showUnavailable') {
$this->assertSame(0, intval($option['value']));
} elseif ($option['option'] == 'name') {
$this->assertSame('Test Menu Board Widget', $option['value']);
} elseif ($option['option'] == 'productsHighlight') {
$this->assertSame([$this->menuBoardProduct['menuBoardProductId']], $option['value']);
}
}
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboLibrary;
use Xibo\OAuth2\Client\Entity\XiboPdf;
use Xibo\OAuth2\Client\Entity\XiboPlaylist;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class PdfWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class PdfWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var XiboLibrary */
protected $media;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create some media to upload
$this->media = (new XiboLibrary($this->getEntityProvider()))->create(Random::generateString(), PROJECT_ROOT . '/tests/resources/sampleDocument.pdf');
// Assign the media we've created to our regions playlist.
$playlist = (new XiboPlaylist($this->getEntityProvider()))->assign([$this->media->mediaId], 10, $layout->regions[0]->regionPlaylist->playlistId);
// Store the widgetId
$this->widgetId = $playlist->widgets[0]->widgetId;
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
// Tidy up the media
$this->media->delete();
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* Test Edit
*/
public function testEdit()
{
$this->getLogger()->debug('testEdit ' . get_class($this) .' Test');
$name = 'Edited Name: ' . Random::generateString(5);
$duration = 80;
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'name' => $name,
'duration' => $duration,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->getLogger()->debug('testEdit Finished');
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboPdf $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboPdf($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($name, $checkWidget->name);
$this->assertSame($duration, $checkWidget->duration);
$this->assertSame($this->media->mediaId, intval($checkWidget->mediaIds[0]));
$this->getLogger()->debug('testEdit Finished');
}
}

View File

@@ -0,0 +1,139 @@
<?php
/*
* Copyright (C) 2025 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Tests\Integration\Widget;
use Xibo\Helper\Random;
use Xibo\OAuth2\Client\Entity\XiboCommand;
use Xibo\OAuth2\Client\Entity\XiboShellCommand;
use Xibo\Tests\Helper\LayoutHelperTrait;
use Xibo\Tests\LocalWebTestCase;
/**
* Class ShellCommandWidgetTest
* @package Xibo\Tests\Integration\Widget
*/
class ShellCommandWidgetTest extends LocalWebTestCase
{
use LayoutHelperTrait;
/** @var \Xibo\OAuth2\Client\Entity\XiboLayout */
protected $publishedLayout;
/** @var XiboCommand */
protected $command;
/** @var int */
protected $widgetId;
/**
* setUp - called before every test automatically
*/
public function setup()
{
parent::setup();
$this->getLogger()->debug('Setup for ' . get_class($this) .' Test');
// Create a Layout
$this->publishedLayout = $this->createLayout();
// Checkout
$layout = $this->getDraft($this->publishedLayout);
// Create a Widget for us to edit.
$response = $this->getEntityProvider()->post('/playlist/widget/shellcommand/' . $layout->regions[0]->regionPlaylist->playlistId);
// Create a command
$this->command = (new XiboCommand($this->getEntityProvider()))->create(Random::generateString(), 'phpunit description', 'phpunitcode');
$this->widgetId = $response['widgetId'];
}
/**
* tearDown - called after every test automatically
*/
public function tearDown()
{
// Delete the Layout we've been working with
$this->deleteLayout($this->publishedLayout);
// Delete the commant
$this->command->delete();
parent::tearDown();
$this->getLogger()->debug('Tear down for ' . get_class($this) .' Test');
}
/**
* Each array is a test run
* Format ($name, $duration, $windowsCommand, $linuxCommand, $launchThroughCmd, $terminateCommand, $useTaskkill, $commandCode)
* @return array
*/
public function provideSuccessCases()
{
# Sets of data used in testAdd
return [
'Windows new command' => ['Api Windows command', 20, 1,'reboot', NULL, 1, null, 1, null],
'Android new command' => ['Api Android command', 30, 1, null, 'reboot', null, 1, null, null],
'Previously created command' => ['Api shell command', 50, 1, null, null, 1, 1, 1, 'phpunit code']
];
}
/**
* @throws \Xibo\OAuth2\Client\Exception\XiboApiException
* @dataProvider provideSuccessCases
*/
public function testEdit($name, $duration, $useDuration, $windowsCommand, $linuxCommand, $launchThroughCmd, $terminateCommand, $useTaskkill, $commandCode)
{
$response = $this->sendRequest('PUT','/playlist/widget/' . $this->widgetId, [
'name' => $name,
'duration' => $duration,
'useDuration' => $useDuration,
'windowsCommand' => $windowsCommand,
'linuxCommand' => $linuxCommand,
'launchThroughCmd' => $launchThroughCmd,
'terminateCommand' => $terminateCommand,
'useTaskkill' => $useTaskkill,
'commandCode' => $commandCode,
], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$this->assertSame(200, $response->getStatusCode());
$this->assertNotEmpty($response->getBody());
$object = json_decode($response->getBody());
$this->assertObjectHasAttribute('data', $object, $response->getBody());
/** @var XiboShellCommand $checkWidget */
$response = $this->getEntityProvider()->get('/playlist/widget', ['widgetId' => $this->widgetId]);
$checkWidget = (new XiboShellCommand($this->getEntityProvider()))->hydrate($response[0]);
$this->assertSame($name, $checkWidget->name);
$this->assertSame($duration, $checkWidget->duration);
foreach ($checkWidget->widgetOptions as $option) {
if ($option['option'] == 'commandCode') {
$this->assertSame($commandCode, $option['value']);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More