2016-03-30 23:37:12 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides a set of targettable permissions for tested models
|
|
|
|
*
|
|
|
|
* @mixin Versioned
|
|
|
|
* @mixin DataObject
|
|
|
|
*/
|
|
|
|
trait ChangeSetTest_Permissions {
|
|
|
|
public function canEdit($member = null) {
|
|
|
|
return $this->can(__FUNCTION__, $member);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function canDelete($member = null) {
|
|
|
|
return $this->can(__FUNCTION__, $member);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function canCreate($member = null, $context = array()) {
|
|
|
|
return $this->can(__FUNCTION__, $member, $context);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function canPublish($member = null, $context = array()) {
|
|
|
|
return $this->can(__FUNCTION__, $member, $context);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function canUnpublish($member = null, $context = array()) {
|
|
|
|
return $this->can(__FUNCTION__, $member, $context);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function can($perm, $member = null, $context = array()) {
|
|
|
|
$perms = [
|
|
|
|
"PERM_{$perm}",
|
|
|
|
'CAN_ALL',
|
|
|
|
];
|
|
|
|
return Permission::checkMember($member, $perms);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin Versioned
|
|
|
|
*/
|
|
|
|
class ChangeSetTest_Base extends DataObject implements TestOnly {
|
|
|
|
use ChangeSetTest_Permissions;
|
|
|
|
|
|
|
|
private static $db = [
|
|
|
|
'Foo' => 'Int',
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $has_many = [
|
|
|
|
'Mids' => 'ChangeSetTest_Mid',
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $owns = [
|
|
|
|
'Mids',
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $extensions = [
|
|
|
|
"Versioned",
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin Versioned
|
|
|
|
*/
|
|
|
|
class ChangeSetTest_Mid extends DataObject implements TestOnly {
|
|
|
|
use ChangeSetTest_Permissions;
|
|
|
|
|
|
|
|
private static $db = [
|
|
|
|
'Bar' => 'Int',
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $has_one = [
|
|
|
|
'Base' => 'ChangeSetTest_Base',
|
|
|
|
'End' => 'ChangeSetTest_End',
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $owns = [
|
|
|
|
'End',
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $extensions = [
|
|
|
|
"Versioned",
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin Versioned
|
|
|
|
*/
|
|
|
|
class ChangeSetTest_End extends DataObject implements TestOnly {
|
|
|
|
use ChangeSetTest_Permissions;
|
|
|
|
|
|
|
|
private static $db = [
|
|
|
|
'Baz' => 'Int',
|
|
|
|
];
|
|
|
|
|
|
|
|
private static $extensions = [
|
|
|
|
"Versioned",
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2016-04-11 22:39:55 +02:00
|
|
|
/**
|
|
|
|
* @mixin Versioned
|
|
|
|
*/
|
|
|
|
class ChangeSetTest_EndChild extends ChangeSetTest_End implements TestOnly {
|
|
|
|
|
|
|
|
private static $db = [
|
|
|
|
'Qux' => 'Int',
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2016-03-30 23:37:12 +02:00
|
|
|
/**
|
|
|
|
* Test {@see ChangeSet} and {@see ChangeSetItem} models
|
|
|
|
*/
|
|
|
|
class ChangeSetTest extends SapphireTest {
|
|
|
|
|
|
|
|
protected static $fixture_file = 'ChangeSetTest.yml';
|
|
|
|
|
|
|
|
protected $extraDataObjects = [
|
|
|
|
'ChangeSetTest_Base',
|
|
|
|
'ChangeSetTest_Mid',
|
|
|
|
'ChangeSetTest_End',
|
2016-04-11 22:39:55 +02:00
|
|
|
'ChangeSetTest_EndChild',
|
2016-03-30 23:37:12 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Automatically publish all objects
|
|
|
|
*/
|
|
|
|
protected function publishAllFixtures() {
|
|
|
|
$this->logInWithPermission('ADMIN');
|
|
|
|
foreach($this->fixtureFactory->getFixtures() as $class => $fixtures) {
|
|
|
|
foreach ($fixtures as $handle => $id) {
|
2016-04-01 05:27:59 +02:00
|
|
|
/** @var Versioned|DataObject $object */
|
|
|
|
$object = $this->objFromFixture($class, $handle);
|
|
|
|
$object->publishSingle();
|
2016-03-30 23:37:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check that the changeset includes the given items
|
|
|
|
*
|
|
|
|
* @param ChangeSet $cs
|
|
|
|
* @param array $match Array of object fixture keys with change type values
|
|
|
|
*/
|
|
|
|
protected function assertChangeSetLooksLike($cs, $match) {
|
|
|
|
$items = $cs->Changes()->toArray();
|
|
|
|
|
|
|
|
foreach($match as $key => $mode) {
|
|
|
|
list($class, $identifier) = explode('.', $key);
|
|
|
|
$object = $this->objFromFixture($class, $identifier);
|
|
|
|
|
|
|
|
foreach($items as $i => $item) {
|
2016-04-11 22:39:55 +02:00
|
|
|
if ($item->ObjectClass == ClassInfo::baseDataClass($object) && $item->ObjectID == $object->ID && $item->Added == $mode) {
|
2016-03-30 23:37:12 +02:00
|
|
|
unset($items[$i]);
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new PHPUnit_Framework_ExpectationFailedException(
|
|
|
|
'Change set didn\'t include expected item',
|
|
|
|
new \SebastianBergmann\Comparator\ComparisonFailure(array('Class' => $class, 'ID' => $object->ID, 'Added' => $mode), null, "$key => $mode", '')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($items)) {
|
|
|
|
$extra = [];
|
|
|
|
foreach ($items as $item) $extra[] = ['Class' => $item->ObjectClass, 'ID' => $item->ObjectID, 'Added' => $item->Added, 'ChangeType' => $item->getChangeType()];
|
|
|
|
throw new PHPUnit_Framework_ExpectationFailedException(
|
|
|
|
'Change set included items that weren\'t expected',
|
|
|
|
new \SebastianBergmann\Comparator\ComparisonFailure(array(), $extra, '', print_r($extra, true))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2016-04-11 22:39:55 +02:00
|
|
|
|
|
|
|
public function testAddObject() {
|
|
|
|
$cs = new ChangeSet();
|
|
|
|
$cs->write();
|
|
|
|
|
|
|
|
$cs->addObject($this->objFromFixture('ChangeSetTest_End', 'end1'));
|
|
|
|
$cs->addObject($this->objFromFixture('ChangeSetTest_EndChild', 'endchild1'));
|
|
|
|
|
|
|
|
$this->assertChangeSetLooksLike($cs, [
|
|
|
|
'ChangeSetTest_End.end1' => ChangeSetItem::EXPLICITLY,
|
|
|
|
'ChangeSetTest_EndChild.endchild1' => ChangeSetItem::EXPLICITLY
|
|
|
|
]);
|
|
|
|
}
|
2016-03-30 23:37:12 +02:00
|
|
|
|
|
|
|
public function testRepeatedSyncIsNOP() {
|
|
|
|
$this->publishAllFixtures();
|
|
|
|
|
|
|
|
$cs = new ChangeSet();
|
|
|
|
$cs->write();
|
|
|
|
|
|
|
|
$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
|
|
|
|
$cs->addObject($base);
|
|
|
|
|
|
|
|
$cs->sync();
|
|
|
|
$this->assertChangeSetLooksLike($cs, [
|
|
|
|
'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY
|
|
|
|
]);
|
|
|
|
|
|
|
|
$cs->sync();
|
|
|
|
$this->assertChangeSetLooksLike($cs, [
|
|
|
|
'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testSync() {
|
|
|
|
$this->publishAllFixtures();
|
|
|
|
|
|
|
|
$cs = new ChangeSet();
|
|
|
|
$cs->write();
|
|
|
|
|
|
|
|
$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
|
|
|
|
|
|
|
|
$cs->addObject($base);
|
|
|
|
$cs->sync();
|
|
|
|
|
|
|
|
$this->assertChangeSetLooksLike($cs, [
|
|
|
|
'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY
|
|
|
|
]);
|
|
|
|
|
|
|
|
$end = $this->objFromFixture('ChangeSetTest_End', 'end1');
|
|
|
|
$end->Baz = 3;
|
|
|
|
$end->write();
|
|
|
|
|
|
|
|
$cs->sync();
|
|
|
|
|
|
|
|
$this->assertChangeSetLooksLike($cs, [
|
|
|
|
'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY,
|
|
|
|
'ChangeSetTest_End.end1' => ChangeSetItem::IMPLICITLY
|
|
|
|
]);
|
|
|
|
|
|
|
|
$endItem = $cs->Changes()->filter('ObjectClass', 'ChangeSetTest_End')->first();
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
[$base->ID],
|
|
|
|
$endItem->ReferencedBy()->column("ID")
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->assertDOSEquals([
|
|
|
|
[
|
|
|
|
'Added' => ChangeSetItem::EXPLICITLY,
|
|
|
|
'ObjectClass' => 'ChangeSetTest_Base',
|
|
|
|
'ObjectID' => $base->ID,
|
|
|
|
'ChangeSetID' => $cs->ID
|
|
|
|
]
|
|
|
|
], $endItem->ReferencedBy());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that sync includes implicit items
|
|
|
|
*/
|
|
|
|
public function testIsSynced() {
|
|
|
|
$this->publishAllFixtures();
|
|
|
|
|
|
|
|
$cs = new ChangeSet();
|
|
|
|
$cs->write();
|
|
|
|
|
|
|
|
$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
|
|
|
|
$cs->addObject($base);
|
|
|
|
|
|
|
|
$cs->sync();
|
|
|
|
$this->assertChangeSetLooksLike($cs, [
|
|
|
|
'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY
|
|
|
|
]);
|
|
|
|
$this->assertTrue($cs->isSynced());
|
|
|
|
|
|
|
|
$end = $this->objFromFixture('ChangeSetTest_End', 'end1');
|
|
|
|
$end->Baz = 3;
|
|
|
|
$end->write();
|
|
|
|
$this->assertFalse($cs->isSynced());
|
|
|
|
|
|
|
|
$cs->sync();
|
|
|
|
|
|
|
|
$this->assertChangeSetLooksLike($cs, [
|
|
|
|
'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY,
|
|
|
|
'ChangeSetTest_End.end1' => ChangeSetItem::IMPLICITLY
|
|
|
|
]);
|
|
|
|
$this->assertTrue($cs->isSynced());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCanPublish() {
|
|
|
|
// Create changeset containing all items (unpublished)
|
|
|
|
$this->logInWithPermission('ADMIN');
|
|
|
|
$changeSet = new ChangeSet();
|
|
|
|
$changeSet->write();
|
|
|
|
$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
|
|
|
|
$changeSet->addObject($base);
|
|
|
|
$changeSet->sync();
|
|
|
|
$this->assertEquals(5, $changeSet->Changes()->count());
|
|
|
|
|
|
|
|
// Test un-authenticated user cannot publish
|
|
|
|
Session::clear("loggedInAs");
|
|
|
|
$this->assertFalse($changeSet->canPublish());
|
|
|
|
|
|
|
|
// User with only one of the necessary permissions cannot publish
|
|
|
|
$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
|
|
|
|
$this->assertFalse($changeSet->canPublish());
|
|
|
|
$this->logInWithPermission('PERM_canPublish');
|
|
|
|
$this->assertFalse($changeSet->canPublish());
|
|
|
|
|
|
|
|
// Test user with the necessary minimum permissions can login
|
|
|
|
$this->logInWithPermission([
|
|
|
|
'CMS_ACCESS_CampaignAdmin',
|
|
|
|
'PERM_canPublish'
|
|
|
|
]);
|
|
|
|
$this->assertTrue($changeSet->canPublish());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCanRevert() {
|
|
|
|
$this->markTestSkipped("Requires ChangeSet::revert to be implemented first");
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCanEdit() {
|
|
|
|
// Create changeset containing all items (unpublished)
|
|
|
|
$this->logInWithPermission('ADMIN');
|
|
|
|
$changeSet = new ChangeSet();
|
|
|
|
$changeSet->write();
|
|
|
|
$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
|
|
|
|
$changeSet->addObject($base);
|
|
|
|
$changeSet->sync();
|
|
|
|
$this->assertEquals(5, $changeSet->Changes()->count());
|
|
|
|
|
|
|
|
// Check canEdit
|
|
|
|
Session::clear("loggedInAs");
|
|
|
|
$this->assertFalse($changeSet->canEdit());
|
|
|
|
$this->logInWithPermission('SomeWrongPermission');
|
|
|
|
$this->assertFalse($changeSet->canEdit());
|
|
|
|
$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
|
|
|
|
$this->assertTrue($changeSet->canEdit());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCanCreate() {
|
|
|
|
// Check canCreate
|
|
|
|
Session::clear("loggedInAs");
|
|
|
|
$this->assertFalse(ChangeSet::singleton()->canCreate());
|
|
|
|
$this->logInWithPermission('SomeWrongPermission');
|
|
|
|
$this->assertFalse(ChangeSet::singleton()->canCreate());
|
|
|
|
$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
|
|
|
|
$this->assertTrue(ChangeSet::singleton()->canCreate());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCanDelete() {
|
|
|
|
// Create changeset containing all items (unpublished)
|
|
|
|
$this->logInWithPermission('ADMIN');
|
|
|
|
$changeSet = new ChangeSet();
|
|
|
|
$changeSet->write();
|
|
|
|
$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
|
|
|
|
$changeSet->addObject($base);
|
|
|
|
$changeSet->sync();
|
|
|
|
$this->assertEquals(5, $changeSet->Changes()->count());
|
|
|
|
|
|
|
|
// Check canDelete
|
|
|
|
Session::clear("loggedInAs");
|
|
|
|
$this->assertFalse($changeSet->canDelete());
|
|
|
|
$this->logInWithPermission('SomeWrongPermission');
|
|
|
|
$this->assertFalse($changeSet->canDelete());
|
|
|
|
$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
|
|
|
|
$this->assertTrue($changeSet->canDelete());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCanView() {
|
|
|
|
// Create changeset containing all items (unpublished)
|
|
|
|
$this->logInWithPermission('ADMIN');
|
|
|
|
$changeSet = new ChangeSet();
|
|
|
|
$changeSet->write();
|
|
|
|
$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
|
|
|
|
$changeSet->addObject($base);
|
|
|
|
$changeSet->sync();
|
|
|
|
$this->assertEquals(5, $changeSet->Changes()->count());
|
|
|
|
|
|
|
|
// Check canView
|
|
|
|
Session::clear("loggedInAs");
|
|
|
|
$this->assertFalse($changeSet->canView());
|
|
|
|
$this->logInWithPermission('SomeWrongPermission');
|
|
|
|
$this->assertFalse($changeSet->canView());
|
|
|
|
$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
|
|
|
|
$this->assertTrue($changeSet->canView());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testPublish() {
|
|
|
|
$this->publishAllFixtures();
|
|
|
|
|
|
|
|
$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
|
|
|
|
$baseID = $base->ID;
|
|
|
|
$baseBefore = $base->Version;
|
|
|
|
$end1 = $this->objFromFixture('ChangeSetTest_End', 'end1');
|
|
|
|
$end1ID = $end1->ID;
|
|
|
|
$end1Before = $end1->Version;
|
|
|
|
|
|
|
|
// Create a new changest
|
|
|
|
$changeset = new ChangeSet();
|
|
|
|
$changeset->write();
|
|
|
|
$changeset->addObject($base);
|
|
|
|
$changeset->addObject($end1);
|
|
|
|
|
|
|
|
// Make a lot of changes
|
|
|
|
// - ChangeSetTest_Base.base modified
|
|
|
|
// - ChangeSetTest_End.end1 deleted
|
|
|
|
// - new ChangeSetTest_Mid added
|
|
|
|
$base->Foo = 343;
|
|
|
|
$base->write();
|
|
|
|
$baseAfter = $base->Version;
|
|
|
|
$midNew = new ChangeSetTest_Mid();
|
|
|
|
$midNew->Bar = 39;
|
|
|
|
$midNew->write();
|
|
|
|
$midNewID = $midNew->ID;
|
|
|
|
$midNewAfter = $midNew->Version;
|
|
|
|
$end1->delete();
|
|
|
|
|
|
|
|
$changeset->addObject($midNew);
|
|
|
|
|
|
|
|
// Publish
|
|
|
|
$this->logInWithPermission('ADMIN');
|
|
|
|
$this->assertTrue($changeset->canPublish());
|
|
|
|
$this->assertTrue($changeset->isSynced());
|
|
|
|
$changeset->publish();
|
|
|
|
$this->assertEquals(ChangeSet::STATE_PUBLISHED, $changeset->State);
|
|
|
|
|
|
|
|
// Check each item has the correct before/after version applied
|
|
|
|
$baseChange = $changeset->Changes()->filter([
|
|
|
|
'ObjectClass' => 'ChangeSetTest_Base',
|
|
|
|
'ObjectID' => $baseID,
|
|
|
|
])->first();
|
|
|
|
$this->assertEquals((int)$baseBefore, (int)$baseChange->VersionBefore);
|
|
|
|
$this->assertEquals((int)$baseAfter, (int)$baseChange->VersionAfter);
|
|
|
|
$this->assertEquals((int)$baseChange->VersionBefore + 1, (int)$baseChange->VersionAfter);
|
|
|
|
$this->assertEquals(
|
|
|
|
(int)$baseChange->VersionAfter,
|
|
|
|
(int)Versioned::get_versionnumber_by_stage('ChangeSetTest_Base', Versioned::LIVE, $baseID)
|
|
|
|
);
|
|
|
|
|
|
|
|
$end1Change = $changeset->Changes()->filter([
|
|
|
|
'ObjectClass' => 'ChangeSetTest_End',
|
|
|
|
'ObjectID' => $end1ID,
|
|
|
|
])->first();
|
|
|
|
$this->assertEquals((int)$end1Before, (int)$end1Change->VersionBefore);
|
|
|
|
$this->assertEquals(0, (int)$end1Change->VersionAfter);
|
|
|
|
$this->assertEquals(
|
|
|
|
0,
|
|
|
|
(int)Versioned::get_versionnumber_by_stage('ChangeSetTest_End', Versioned::LIVE, $end1ID)
|
|
|
|
);
|
|
|
|
|
|
|
|
$midNewChange = $changeset->Changes()->filter([
|
|
|
|
'ObjectClass' => 'ChangeSetTest_Mid',
|
|
|
|
'ObjectID' => $midNewID,
|
|
|
|
])->first();
|
|
|
|
$this->assertEquals(0, (int)$midNewChange->VersionBefore);
|
|
|
|
$this->assertEquals((int)$midNewAfter, (int)$midNewChange->VersionAfter);
|
|
|
|
$this->assertEquals(
|
|
|
|
(int)$midNewAfter,
|
|
|
|
(int)Versioned::get_versionnumber_by_stage('ChangeSetTest_Mid', Versioned::LIVE, $midNewID)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Test trying to re-publish is blocked
|
|
|
|
$this->setExpectedException(
|
|
|
|
'BadMethodCallException',
|
|
|
|
"ChangeSet can't be published if it has been already published or reverted."
|
|
|
|
);
|
|
|
|
$changeset->publish();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|