diff --git a/appinfo/info.xml b/appinfo/info.xml
index 2e534010c..261f763a5 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -1,96 +1,97 @@
-
-
-
- deck
- Deck
- Personal planning and team project organization
- Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.
-
-
-- ๐ฅ Add your tasks to cards and put them in order
-- ๐ Write down additional notes in Markdown
-- ๐ Assign labels for even better organization
-- ๐ฅ Share with your team, friends or family
-- ๐ Attach files and embed them in your Markdown description
-- ๐ฌ Discuss with your team using comments
-- โก Keep track of changes in the activity stream
-- ๐ Get your project organized
-
-
- 4.0.0-dev.1
- agpl
- Julius Hรคrtl
- Deck
-
-
-
-
- https://deck.readthedocs.io/en/latest/User_documentation_en/
- https://deck.readthedocs.io/en/latest/API/
-
- organization
- office
- https://github.com/nextcloud/deck
- https://github.com/nextcloud/deck/issues
- https://github.com/nextcloud/deck.git
- https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-1.png
- https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png
-
- pgsql
- sqlite
- mysql
-
-
-
- OCA\Deck\Cron\DeleteCron
- OCA\Deck\Cron\ScheduledNotifications
- OCA\Deck\Cron\CardDescriptionActivity
- OCA\Deck\Cron\SessionsCleanup
-
-
-
- OCA\Deck\Migration\DeletedCircleCleanup
-
-
- OCA\Deck\Migration\LabelMismatchCleanup
-
-
-
- OCA\Deck\Command\UserExport
- OCA\Deck\Command\BoardImport
- OCA\Deck\Command\TransferOwnership
- OCA\Deck\Command\CalendarToggle
-
-
-
- OCA\Deck\Activity\SettingChanges
- OCA\Deck\Activity\SettingDescription
- OCA\Deck\Activity\SettingComment
-
-
- OCA\Deck\Activity\Filter
-
-
- OCA\Deck\Activity\DeckProvider
-
-
-
- OCA\Deck\Provider\DeckProvider
-
-
-
- Deck
- deck.page.index
- deck.svg
- 10
-
-
-
-
- OCA\Deck\DAV\CalendarPlugin
-
-
-
+
+
+
+ deck
+ Deck
+ Personal planning and team project organization
+ Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.
+
+
+- ๐ฅ Add your tasks to cards and put them in order
+- ๐ Write down additional notes in Markdown
+- ๐ Assign labels for even better organization
+- ๐ฅ Share with your team, friends or family
+- ๐ Attach files and embed them in your Markdown description
+- ๐ฌ Discuss with your team using comments
+- โก Keep track of changes in the activity stream
+- ๐ Get your project organized
+
+
+ 4.0.0-dev.2
+ agpl
+ Julius Hรคrtl
+ Deck
+
+
+
+
+ https://deck.readthedocs.io/en/latest/User_documentation_en/
+ https://deck.readthedocs.io/en/latest/API/
+
+ organization
+ office
+ https://github.com/nextcloud/deck
+ https://github.com/nextcloud/deck/issues
+ https://github.com/nextcloud/deck.git
+ https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-1.png
+ https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png
+
+ pgsql
+ sqlite
+ mysql
+
+
+
+ OCA\Deck\Cron\DeleteCron
+ OCA\Deck\Cron\ScheduledNotifications
+ OCA\Deck\Cron\CardDescriptionActivity
+ OCA\Deck\Cron\SessionsCleanup
+
+
+
+ OCA\Deck\Migration\DeletedCircleCleanup
+
+
+ OCA\Deck\Migration\LabelMismatchCleanup
+ OCA\Deck\Migration\AclTimestampBackfill
+
+
+
+ OCA\Deck\Command\UserExport
+ OCA\Deck\Command\BoardImport
+ OCA\Deck\Command\TransferOwnership
+ OCA\Deck\Command\CalendarToggle
+
+
+
+ OCA\Deck\Activity\SettingChanges
+ OCA\Deck\Activity\SettingDescription
+ OCA\Deck\Activity\SettingComment
+
+
+ OCA\Deck\Activity\Filter
+
+
+ OCA\Deck\Activity\DeckProvider
+
+
+
+ OCA\Deck\Provider\DeckProvider
+
+
+
+ Deck
+ deck.page.index
+ deck.svg
+ 10
+
+
+
+
+ OCA\Deck\DAV\CalendarPlugin
+
+
+
diff --git a/lib/Db/Acl.php b/lib/Db/Acl.php
index 735f9a19f..52aba43b1 100644
--- a/lib/Db/Acl.php
+++ b/lib/Db/Acl.php
@@ -21,6 +21,10 @@
* @method void setOwner(int $owner)
* @method void setToken(string $token)
* @method string getToken()
+ * @method int getCreatedAt()
+ * @method void setCreatedAt(int $createdAt)
+ * @method int getLastModifiedAt()
+ * @method void setLastModifiedAt(int $lastModifiedAt)
*
*/
class Acl extends RelationalEntity {
@@ -42,6 +46,8 @@ class Acl extends RelationalEntity {
protected $permissionManage = false;
protected $owner = false;
protected $token = null;
+ protected $createdAt = 0;
+ protected $lastModifiedAt = 0;
public function __construct() {
$this->addType('id', 'integer');
@@ -52,6 +58,8 @@ public function __construct() {
$this->addType('type', 'integer');
$this->addType('owner', 'boolean');
$this->addType('token', 'string');
+ $this->addType('createdAt', 'integer');
+ $this->addType('lastModifiedAt', 'integer');
$this->addRelation('owner');
$this->addResolvable('participant');
}
diff --git a/lib/Db/AclMapper.php b/lib/Db/AclMapper.php
index 669ca7a5e..defba19d1 100644
--- a/lib/Db/AclMapper.php
+++ b/lib/Db/AclMapper.php
@@ -8,6 +8,7 @@
namespace OCA\Deck\Db;
use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@@ -20,7 +21,7 @@ public function __construct(IDBConnection $db) {
public function findByAccessToken(string $accessToken) {
$qb = $this->db->getQueryBuilder();
- $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'token')
+ $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'token', 'created_at', 'last_modified_at')
->from('deck_board_acl')
->where($qb->expr()->eq('token', $qb->createNamedParameter($accessToken, IQueryBuilder::PARAM_STR)))
->setMaxResults(1);
@@ -34,7 +35,7 @@ public function findByAccessToken(string $accessToken) {
*/
public function findAll(int $boardId, ?int $limit = null, ?int $offset = null) {
$qb = $this->db->getQueryBuilder();
- $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'token')
+ $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'token', 'created_at', 'last_modified_at')
->from('deck_board_acl')
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->setMaxResults($limit)
@@ -45,7 +46,7 @@ public function findAll(int $boardId, ?int $limit = null, ?int $offset = null) {
public function findIn(array $boardIds, ?int $limit = null, ?int $offset = null): array {
$qb = $this->db->getQueryBuilder();
- $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
+ $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'created_at', 'last_modified_at')
->from('deck_board_acl')
->where($qb->expr()->in('board_id', $qb->createParameter('boardIds')))
->setMaxResults($limit)
@@ -127,4 +128,18 @@ public function findByType(int $type): array {
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
return $this->findEntities($qb);
}
+
+ public function insert(Entity $entity): Entity {
+ /** @var Acl $entity */
+ $now = time();
+ $entity->setCreatedAt($now);
+ $entity->setLastModifiedAt($now);
+ return parent::insert($entity);
+ }
+
+ public function update(Entity $entity): Entity {
+ /** @var Acl $entity */
+ $entity->setLastModifiedAt(time());
+ return parent::update($entity);
+ }
}
diff --git a/lib/Migration/AclTimestampBackfill.php b/lib/Migration/AclTimestampBackfill.php
new file mode 100644
index 000000000..aa49688d4
--- /dev/null
+++ b/lib/Migration/AclTimestampBackfill.php
@@ -0,0 +1,68 @@
+db->getQueryBuilder();
+ $selectQb->select('a.id AS acl_id', 'b.last_modified AS board_last_modified')
+ ->from('deck_board_acl', 'a')
+ ->join('a', 'deck_boards', 'b', $selectQb->expr()->eq('b.id', 'a.board_id'))
+ ->where($selectQb->expr()->eq('a.created_at', $selectQb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
+
+ $result = $selectQb->executeQuery();
+ $rows = $result->fetchAll();
+ $result->closeCursor();
+
+ if ($rows === []) {
+ $output->info('AclTimestampBackfill: no rows to update');
+ return;
+ }
+
+ // Group ACL IDs by target timestamp to issue one UPDATE per group
+ // rather than one UPDATE per row (avoids N+1 queries).
+ $now = time();
+ $groups = [];
+ foreach ($rows as $row) {
+ $timestamp = ((int)$row['board_last_modified'] > 0) ? (int)$row['board_last_modified'] : $now;
+ $groups[$timestamp][] = (int)$row['acl_id'];
+ }
+
+ $updated = 0;
+ foreach ($groups as $timestamp => $ids) {
+ // Chunk at 1000 for Oracle compatibility (same limit used by chunkQuery).
+ foreach (array_chunk($ids, 1000) as $chunk) {
+ $updateQb = $this->db->getQueryBuilder();
+ $updateQb->update('deck_board_acl')
+ ->set('created_at', $updateQb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
+ ->set('last_modified_at', $updateQb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
+ ->where($updateQb->expr()->in('id', $updateQb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY)));
+ $updateQb->executeStatement();
+ $updated += count($chunk);
+ }
+ }
+
+ $output->info('AclTimestampBackfill: updated ' . $updated . ' row(s)');
+ }
+}
diff --git a/lib/Migration/Version11002Date20260611000000.php b/lib/Migration/Version11002Date20260611000000.php
new file mode 100644
index 000000000..79b1cd601
--- /dev/null
+++ b/lib/Migration/Version11002Date20260611000000.php
@@ -0,0 +1,41 @@
+getTable('deck_board_acl');
+
+ if (!$table->hasColumn('created_at')) {
+ $table->addColumn('created_at', 'integer', [
+ 'notnull' => true,
+ 'default' => 0,
+ 'unsigned' => true,
+ ]);
+ }
+
+ if (!$table->hasColumn('last_modified_at')) {
+ $table->addColumn('last_modified_at', 'integer', [
+ 'notnull' => true,
+ 'default' => 0,
+ 'unsigned' => true,
+ ]);
+ }
+
+ return $schema;
+ }
+}
diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php
index 44bc4e29b..1610352ae 100644
--- a/lib/Service/BoardService.php
+++ b/lib/Service/BoardService.php
@@ -400,6 +400,9 @@ public function addAcl(int $boardId, int $type, $participant, bool $edit, bool $
$acl->setPermissionEdit($edit);
$acl->setPermissionShare($share);
$acl->setPermissionManage($manage);
+ $now = time();
+ $acl->setCreatedAt($now);
+ $acl->setLastModifiedAt($now);
$newAcl = $this->aclMapper->insert($acl);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId);
@@ -451,6 +454,7 @@ public function updateAcl(int $id, bool $edit, bool $share, bool $manage): Acl {
$acl->setPermissionEdit($edit);
$acl->setPermissionShare($share);
$acl->setPermissionManage($manage);
+ $acl->setLastModifiedAt(time());
$this->boardMapper->mapAcl($acl);
$acl = $this->aclMapper->update($acl);
$this->changeHelper->boardChanged($acl->getBoardId());
diff --git a/tests/integration/base-query-count.txt b/tests/integration/base-query-count.txt
index ce8c60422..627806590 100644
--- a/tests/integration/base-query-count.txt
+++ b/tests/integration/base-query-count.txt
@@ -1 +1 @@
-93102
+96706
\ No newline at end of file
diff --git a/tests/integration/import/ImportExportTest.php b/tests/integration/import/ImportExportTest.php
index a9e00e0cb..586c11c3a 100644
--- a/tests/integration/import/ImportExportTest.php
+++ b/tests/integration/import/ImportExportTest.php
@@ -134,7 +134,7 @@ public function testReimportOcc() {
);
}
- public static function writeArrayStructure(string $prefix = '', array $array = [], array $skipKeyList = ['id', 'boardId', 'cardId', 'stackId', 'ETag', 'permissions', 'shared', 'version', 'done', 'referenceData', 'token']): string {
+ public static function writeArrayStructure(string $prefix = '', array $array = [], array $skipKeyList = ['id', 'boardId', 'cardId', 'stackId', 'ETag', 'permissions', 'shared', 'version', 'done', 'referenceData', 'token', 'createdAt', 'lastModifiedAt']): string {
$output = '';
$arrayIsList = array_keys($array) === range(0, count($array) - 1);
foreach ($array as $key => $value) {
diff --git a/tests/unit/Db/AclMapperTest.php b/tests/unit/Db/AclMapperTest.php
index af3f03196..0154ee62b 100644
--- a/tests/unit/Db/AclMapperTest.php
+++ b/tests/unit/Db/AclMapperTest.php
@@ -120,6 +120,34 @@ public function testFindBoardIdDatabase() {
$this->assertEquals($this->boards[0]->getId(), $this->aclMapper->findBoardId($this->acls[1]->getId()));
}
+ public function testInsertSetsCreatedAtAndLastModifiedAt(): void {
+ $before = time();
+ $acl = $this->getAcl('user', 'timestamps_user', false, false, false, $this->boards[0]->getId());
+ $inserted = $this->aclMapper->insert($acl);
+ $after = time();
+
+ $this->assertGreaterThanOrEqual($before, $inserted->getCreatedAt());
+ $this->assertLessThanOrEqual($after, $inserted->getCreatedAt());
+ $this->assertGreaterThanOrEqual($before, $inserted->getLastModifiedAt());
+ $this->assertLessThanOrEqual($after, $inserted->getLastModifiedAt());
+
+ $this->aclMapper->delete($inserted);
+ }
+
+ public function testUpdateChangesLastModifiedAtButNotCreatedAt(): void {
+ $acl = $this->getAcl('user', 'timestamps_user2', false, false, false, $this->boards[0]->getId());
+ $inserted = $this->aclMapper->insert($acl);
+ $originalCreatedAt = $inserted->getCreatedAt();
+
+ $inserted->setPermissionEdit(true);
+ $updated = $this->aclMapper->update($inserted);
+
+ $this->assertSame($originalCreatedAt, $updated->getCreatedAt());
+ $this->assertGreaterThan(0, $updated->getLastModifiedAt());
+
+ $this->aclMapper->delete($updated);
+ }
+
public function tearDown(): void {
parent::tearDown();
foreach ($this->acls as $acl) {
diff --git a/tests/unit/Db/AclTest.php b/tests/unit/Db/AclTest.php
index e1538215e..069cb98dd 100644
--- a/tests/unit/Db/AclTest.php
+++ b/tests/unit/Db/AclTest.php
@@ -59,7 +59,9 @@ public function testJsonSerialize() {
'permissionEdit' => true,
'permissionShare' => true,
'permissionManage' => true,
- 'owner' => false
+ 'owner' => false,
+ 'createdAt' => 0,
+ 'lastModifiedAt' => 0,
], $acl->jsonSerialize());
$acl = $this->createAclGroup();
$this->assertEquals([
@@ -70,7 +72,9 @@ public function testJsonSerialize() {
'permissionEdit' => true,
'permissionShare' => true,
'permissionManage' => true,
- 'owner' => false
+ 'owner' => false,
+ 'createdAt' => 0,
+ 'lastModifiedAt' => 0,
], $acl->jsonSerialize());
}
@@ -85,7 +89,9 @@ public function testSetOwner() {
'permissionEdit' => true,
'permissionShare' => true,
'permissionManage' => true,
- 'owner' => true
+ 'owner' => true,
+ 'createdAt' => 0,
+ 'lastModifiedAt' => 0,
], $acl->jsonSerialize());
}
diff --git a/tests/unit/Migration/AclTimestampBackfillTest.php b/tests/unit/Migration/AclTimestampBackfillTest.php
new file mode 100644
index 000000000..cc9ddc09e
--- /dev/null
+++ b/tests/unit/Migration/AclTimestampBackfillTest.php
@@ -0,0 +1,181 @@
+db = $this->createMock(IDBConnection::class);
+ $this->output = $this->createMock(IOutput::class);
+ $this->backfill = new AclTimestampBackfill($this->db);
+ }
+
+ public function testGetName(): void {
+ $this->assertNotEmpty($this->backfill->getName());
+ }
+
+ public function testRunNoRowsIsNoop(): void {
+ [$selectQb] = $this->buildSelectQb([]);
+
+ $this->db->method('getQueryBuilder')->willReturn($selectQb);
+ $this->output->expects($this->once())
+ ->method('info')
+ ->with($this->stringContains('no rows'));
+
+ $this->backfill->run($this->output);
+ }
+
+ public function testRunGroupsRowsWithSameTimestampIntoOneUpdate(): void {
+ // Two ACLs from the same board share the same timestamp โ only 1 UPDATE
+ $rows = [
+ ['acl_id' => 1, 'board_last_modified' => 1000000],
+ ['acl_id' => 2, 'board_last_modified' => 1000000],
+ ];
+ [$selectQb] = $this->buildSelectQb($rows);
+ $updateQb = $this->buildUpdateQb(1); // single IN-clause UPDATE for both IDs
+
+ $this->db->expects($this->exactly(2)) // 1 SELECT + 1 UPDATE
+ ->method('getQueryBuilder')
+ ->willReturnOnConsecutiveCalls($selectQb, $updateQb);
+
+ $this->output->expects($this->once())
+ ->method('info')
+ ->with($this->stringContains('2'));
+
+ $this->backfill->run($this->output);
+ }
+
+ public function testRunIssuesSeparateUpdatePerDistinctTimestamp(): void {
+ // Two ACLs from different boards โ two distinct timestamps โ 2 UPDATEs
+ $rows = [
+ ['acl_id' => 1, 'board_last_modified' => 1000000],
+ ['acl_id' => 2, 'board_last_modified' => 2000000],
+ ];
+ [$selectQb] = $this->buildSelectQb($rows);
+ $updateQb1 = $this->buildUpdateQb(1);
+ $updateQb2 = $this->buildUpdateQb(1);
+
+ $this->db->expects($this->exactly(3)) // 1 SELECT + 2 UPDATEs
+ ->method('getQueryBuilder')
+ ->willReturnOnConsecutiveCalls($selectQb, $updateQb1, $updateQb2);
+
+ $this->output->expects($this->once())
+ ->method('info')
+ ->with($this->stringContains('2'));
+
+ $this->backfill->run($this->output);
+ }
+
+ public function testRunUsesBoardTimestampWhenAvailable(): void {
+ $rows = [['acl_id' => 1, 'board_last_modified' => 1234567]];
+ [$selectQb] = $this->buildSelectQb($rows);
+
+ $capturedTs = null;
+ $updateQb = $this->buildUpdateQb(1, function (mixed $value, int $type) use (&$capturedTs): void {
+ if ($type === IQueryBuilder::PARAM_INT) {
+ $capturedTs = $value;
+ }
+ });
+
+ $this->db->expects($this->exactly(2))
+ ->method('getQueryBuilder')
+ ->willReturnOnConsecutiveCalls($selectQb, $updateQb);
+ $this->output->method('info');
+
+ $this->backfill->run($this->output);
+
+ $this->assertSame(1234567, $capturedTs);
+ }
+
+ public function testRunUsesCurrentTimeWhenBoardTimestampIsZero(): void {
+ $rows = [['acl_id' => 1, 'board_last_modified' => 0]];
+ [$selectQb] = $this->buildSelectQb($rows);
+
+ $capturedTs = null;
+ $before = time();
+ $updateQb = $this->buildUpdateQb(1, function (mixed $value, int $type) use (&$capturedTs): void {
+ if ($type === IQueryBuilder::PARAM_INT) {
+ $capturedTs = $value;
+ }
+ });
+
+ $this->db->expects($this->exactly(2))
+ ->method('getQueryBuilder')
+ ->willReturnOnConsecutiveCalls($selectQb, $updateQb);
+ $this->output->method('info');
+
+ $this->backfill->run($this->output);
+ $after = time();
+
+ $this->assertGreaterThanOrEqual($before, $capturedTs);
+ $this->assertLessThanOrEqual($after, $capturedTs);
+ }
+
+ /**
+ * @return array{0: IQueryBuilder&MockObject}
+ */
+ private function buildSelectQb(array $rows): array {
+ $expr = $this->createMock(IExpressionBuilder::class);
+ $expr->method('eq')->willReturn('1=1');
+
+ $result = $this->createMock(IResult::class);
+ $result->method('fetchAll')->willReturn($rows);
+ $result->expects($this->once())->method('closeCursor');
+
+ $qb = $this->createMock(IQueryBuilder::class);
+ $qb->method('select')->willReturnSelf();
+ $qb->method('from')->willReturnSelf();
+ $qb->method('join')->willReturnSelf();
+ $qb->method('where')->willReturnSelf();
+ $qb->method('createNamedParameter')->willReturn('?');
+ $qb->method('expr')->willReturn($expr);
+ $qb->method('executeQuery')->willReturn($result);
+
+ return [$qb];
+ }
+
+ private function buildUpdateQb(int $expectedExecutions, ?\Closure $onCreateNamedParameter = null): IQueryBuilder&MockObject {
+ $expr = $this->createMock(IExpressionBuilder::class);
+ $expr->method('in')->willReturn('1=1');
+
+ $qb = $this->createMock(IQueryBuilder::class);
+ $qb->method('update')->willReturnSelf();
+ $qb->method('set')->willReturnSelf();
+ $qb->method('where')->willReturnSelf();
+ $qb->method('expr')->willReturn($expr);
+ $qb->expects($this->exactly($expectedExecutions))->method('executeStatement');
+
+ if ($onCreateNamedParameter !== null) {
+ $qb->method('createNamedParameter')->willReturnCallback(
+ function (mixed $value, int $type) use ($onCreateNamedParameter): string {
+ $onCreateNamedParameter($value, $type);
+ return '?';
+ }
+ );
+ } else {
+ $qb->method('createNamedParameter')->willReturn('?');
+ }
+
+ return $qb;
+ }
+}
diff --git a/tests/unit/Service/BoardServiceTest.php b/tests/unit/Service/BoardServiceTest.php
index 549021bf0..e28a133a7 100644
--- a/tests/unit/Service/BoardServiceTest.php
+++ b/tests/unit/Service/BoardServiceTest.php
@@ -301,7 +301,16 @@ public function testAddAcl() {
->method('sendBoardShared');
$this->aclMapper->expects($this->once())
->method('insert')
- ->with($acl)
+ ->with($this->callback(function (Acl $actual) use ($acl) {
+ return $actual->getBoardId() === $acl->getBoardId()
+ && $actual->getType() === $acl->getType()
+ && $actual->getParticipant() === $acl->getParticipant()
+ && $actual->getPermissionEdit() === $acl->getPermissionEdit()
+ && $actual->getPermissionShare() === $acl->getPermissionShare()
+ && $actual->getPermissionManage() === $acl->getPermissionManage()
+ && $actual->getCreatedAt() > 0
+ && $actual->getLastModifiedAt() > 0;
+ }))
->willReturn($acl);
$this->permissionService->expects($this->any())
->method('findUsers')
@@ -407,11 +416,30 @@ public function testAddAclExtendPermission($currentUserAcl, $providedAcl, $resul
$expected = clone $acl;
$this->aclMapper->expects($this->once())
->method('insert')
- ->with($acl)
+ ->with($this->callback(function (Acl $actual) use ($acl) {
+ return $actual->getBoardId() === $acl->getBoardId()
+ && $actual->getType() === $acl->getType()
+ && $actual->getParticipant() === $acl->getParticipant()
+ && $actual->getPermissionEdit() === $acl->getPermissionEdit()
+ && $actual->getPermissionShare() === $acl->getPermissionShare()
+ && $actual->getPermissionManage() === $acl->getPermissionManage()
+ && $actual->getCreatedAt() > 0
+ && $actual->getLastModifiedAt() > 0;
+ }))
->willReturn($acl);
$this->eventDispatcher->expects(self::once())
->method('dispatchTyped')
- ->with(new AclCreatedEvent($acl));
+ ->with($this->callback(function (AclCreatedEvent $event) use ($acl) {
+ $eventAcl = $event->getAcl();
+ return $eventAcl->getBoardId() === $acl->getBoardId()
+ && $eventAcl->getType() === $acl->getType()
+ && $eventAcl->getParticipant() === $acl->getParticipant()
+ && $eventAcl->getPermissionEdit() === $acl->getPermissionEdit()
+ && $eventAcl->getPermissionShare() === $acl->getPermissionShare()
+ && $eventAcl->getPermissionManage() === $acl->getPermissionManage()
+ && $eventAcl->getCreatedAt() > 0
+ && $eventAcl->getLastModifiedAt() > 0;
+ }));
$this->assertEquals($expected, $this->service->addAcl(
123, Acl::PERMISSION_TYPE_USER, 'admin', $providedAcl[0], $providedAcl[1], $providedAcl[2]
));