@@ -1587,93 +1587,95 @@ public function testUpdateDocumentChangeIdMigratesPermissions(): void
15871587
15881588 $ collection = 'update_change_id_perms ' ;
15891589
1590- // documentSecurity with no collection-level permissions: reads are
1591- // governed purely by the document's rows in the _perms table.
1592- $ database ->createCollection ($ collection , permissions: [], documentSecurity: true );
1593- $ this ->assertEquals (true , $ database ->createAttribute ($ collection , 'name ' , Database::VAR_STRING , 128 , false ));
1594-
1595- // Create a document whose read permission is scoped to a single role,
1596- // so that find() must consult the _perms table to return it.
1597- $ document = $ auth ->skip (fn () => $ database ->createDocument ($ collection , new Document ([
1598- '$id ' => 'old_id ' ,
1599- 'name ' => 'test ' ,
1600- '$permissions ' => [
1601- Permission::read (Role::user ('alice ' )),
1602- Permission::update (Role::user ('alice ' )),
1603- Permission::delete (Role::user ('alice ' )),
1604- ],
1605- ])));
1606- $ this ->assertEquals ('old_id ' , $ document ->getId ());
1607-
1608- // Sanity: as alice the document is visible via the _perms table.
1609- $ auth ->addRole (Role::user ('alice ' )->toString ());
1610- $ this ->assertCount (1 , $ database ->find ($ collection ));
1611-
1612- // Rename the document WITHOUT changing its permission set.
1613- $ renamed = $ auth ->skip (fn () => $ database ->updateDocument ($ collection , 'old_id ' , new Document (\array_merge (
1614- $ document ->getArrayCopy (),
1615- ['$id ' => 'new_id ' ],
1616- ))));
1617- $ this ->assertEquals ('new_id ' , $ renamed ->getId ());
1618-
1619- // The old UID must no longer resolve to a document.
1620- $ this ->assertTrue ($ auth ->skip (fn () => $ database ->getDocument ($ collection , 'old_id ' ))->isEmpty ());
1621-
1622- // The new UID must exist and keep its permissions on the main row.
1623- $ newDoc = $ auth ->skip (fn () => $ database ->getDocument ($ collection , 'new_id ' ));
1624- $ this ->assertFalse ($ newDoc ->isEmpty ());
1625- $ this ->assertContains (Permission::read (Role::user ('alice ' )), $ newDoc ->getPermissions ());
1626-
1627- // The crucial check: the permission rows must have migrated to the new
1628- // UID in the _perms table. As alice, find() (which joins _perms) must
1629- // still return exactly the renamed document. With orphaned rows under
1630- // the old UID this returns 0.
1631- $ found = $ database ->find ($ collection );
1632- $ this ->assertCount (1 , $ found );
1633- $ this ->assertEquals ('new_id ' , $ found [0 ]->getId ());
1634-
1635- /**
1636- * Second scenario: change the UID AND the permission set in the same
1637- * update. Drop alice's access and grant bob instead. The removed rows
1638- * must be gone, the added rows must land under the new UID, and nothing
1639- * may be left orphaned under the old UID.
1640- */
1641- $ rekeyed = $ auth ->skip (fn () => $ database ->updateDocument ($ collection , 'new_id ' , new Document (\array_merge (
1642- $ newDoc ->getArrayCopy (),
1643- [
1644- '$id ' => 'final_id ' ,
1590+ try {
1591+ // documentSecurity with no collection-level permissions: reads are
1592+ // governed purely by the document's rows in the _perms table.
1593+ $ database ->createCollection ($ collection , permissions: [], documentSecurity: true );
1594+ $ this ->assertEquals (true , $ database ->createAttribute ($ collection , 'name ' , Database::VAR_STRING , 128 , false ));
1595+
1596+ // Create a document whose read permission is scoped to a single role,
1597+ // so that find() must consult the _perms table to return it.
1598+ $ document = $ auth ->skip (fn () => $ database ->createDocument ($ collection , new Document ([
1599+ '$id ' => 'old_id ' ,
1600+ 'name ' => 'test ' ,
16451601 '$permissions ' => [
1646- Permission::read (Role::user ('bob ' )),
1647- Permission::update (Role::user ('bob ' )),
1648- Permission::delete (Role::user ('bob ' )),
1602+ Permission::read (Role::user ('alice ' )),
1603+ Permission::update (Role::user ('alice ' )),
1604+ Permission::delete (Role::user ('alice ' )),
16491605 ],
1650- ],
1651- ))));
1652- $ this ->assertEquals ('final_id ' , $ rekeyed ->getId ());
1653-
1654- // The old UID must no longer resolve to a document.
1655- $ this ->assertTrue ($ auth ->skip (fn () => $ database ->getDocument ($ collection , 'new_id ' ))->isEmpty ());
1606+ ])));
1607+ $ this ->assertEquals ('old_id ' , $ document ->getId ());
1608+
1609+ // Sanity: as alice the document is visible via the _perms table.
1610+ $ auth ->addRole (Role::user ('alice ' )->toString ());
1611+ $ this ->assertCount (1 , $ database ->find ($ collection ));
1612+
1613+ // Rename the document WITHOUT changing its permission set.
1614+ $ renamed = $ auth ->skip (fn () => $ database ->updateDocument ($ collection , 'old_id ' , new Document (\array_merge (
1615+ $ document ->getArrayCopy (),
1616+ ['$id ' => 'new_id ' ],
1617+ ))));
1618+ $ this ->assertEquals ('new_id ' , $ renamed ->getId ());
1619+
1620+ // The old UID must no longer resolve to a document.
1621+ $ this ->assertTrue ($ auth ->skip (fn () => $ database ->getDocument ($ collection , 'old_id ' ))->isEmpty ());
1622+
1623+ // The new UID must exist and keep its permissions on the main row.
1624+ $ newDoc = $ auth ->skip (fn () => $ database ->getDocument ($ collection , 'new_id ' ));
1625+ $ this ->assertFalse ($ newDoc ->isEmpty ());
1626+ $ this ->assertContains (Permission::read (Role::user ('alice ' )), $ newDoc ->getPermissions ());
1627+
1628+ // The crucial check: the permission rows must have migrated to the new
1629+ // UID in the _perms table. As alice, find() (which joins _perms) must
1630+ // still return exactly the renamed document. With orphaned rows under
1631+ // the old UID this returns 0.
1632+ $ found = $ database ->find ($ collection );
1633+ $ this ->assertCount (1 , $ found );
1634+ $ this ->assertEquals ('new_id ' , $ found [0 ]->getId ());
16561635
1657- // The main row must reflect the new permission set.
1658- $ finalDoc = $ auth ->skip (fn () => $ database ->getDocument ($ collection , 'final_id ' ));
1659- $ this ->assertFalse ($ finalDoc ->isEmpty ());
1660- $ this ->assertContains (Permission::read (Role::user ('bob ' )), $ finalDoc ->getPermissions ());
1661- $ this ->assertNotContains (Permission::read (Role::user ('alice ' )), $ finalDoc ->getPermissions ());
1662-
1663- // alice's permission rows were removed: as alice nothing is returned.
1664- $ this ->assertCount (0 , $ database ->find ($ collection ));
1665-
1666- // bob's permission rows landed under the new UID: as bob the renamed
1667- // document is returned via the _perms join.
1668- $ auth ->addRole (Role::user ('bob ' )->toString ());
1669- $ foundAsBob = $ database ->find ($ collection );
1670- $ this ->assertCount (1 , $ foundAsBob );
1671- $ this ->assertEquals ('final_id ' , $ foundAsBob [0 ]->getId ());
1672-
1673- $ auth ->removeRole (Role::user ('alice ' )->toString ());
1674- $ auth ->removeRole (Role::user ('bob ' )->toString ());
1636+ /**
1637+ * Second scenario: change the UID AND the permission set in the same
1638+ * update. Drop alice's access and grant bob instead. The removed rows
1639+ * must be gone, the added rows must land under the new UID, and nothing
1640+ * may be left orphaned under the old UID.
1641+ */
1642+ $ rekeyed = $ auth ->skip (fn () => $ database ->updateDocument ($ collection , 'new_id ' , new Document (\array_merge (
1643+ $ newDoc ->getArrayCopy (),
1644+ [
1645+ '$id ' => 'final_id ' ,
1646+ '$permissions ' => [
1647+ Permission::read (Role::user ('bob ' )),
1648+ Permission::update (Role::user ('bob ' )),
1649+ Permission::delete (Role::user ('bob ' )),
1650+ ],
1651+ ],
1652+ ))));
1653+ $ this ->assertEquals ('final_id ' , $ rekeyed ->getId ());
1654+
1655+ // The old UID must no longer resolve to a document.
1656+ $ this ->assertTrue ($ auth ->skip (fn () => $ database ->getDocument ($ collection , 'new_id ' ))->isEmpty ());
1657+
1658+ // The main row must reflect the new permission set.
1659+ $ finalDoc = $ auth ->skip (fn () => $ database ->getDocument ($ collection , 'final_id ' ));
1660+ $ this ->assertFalse ($ finalDoc ->isEmpty ());
1661+ $ this ->assertContains (Permission::read (Role::user ('bob ' )), $ finalDoc ->getPermissions ());
1662+ $ this ->assertNotContains (Permission::read (Role::user ('alice ' )), $ finalDoc ->getPermissions ());
1663+
1664+ // alice's permission rows were removed: as alice nothing is returned.
1665+ $ this ->assertCount (0 , $ database ->find ($ collection ));
1666+
1667+ // bob's permission rows landed under the new UID: as bob the renamed
1668+ // document is returned via the _perms join.
1669+ $ auth ->addRole (Role::user ('bob ' )->toString ());
1670+ $ foundAsBob = $ database ->find ($ collection );
1671+ $ this ->assertCount (1 , $ foundAsBob );
1672+ $ this ->assertEquals ('final_id ' , $ foundAsBob [0 ]->getId ());
1673+ } finally {
1674+ $ auth ->removeRole (Role::user ('alice ' )->toString ());
1675+ $ auth ->removeRole (Role::user ('bob ' )->toString ());
16751676
1676- $ auth ->skip (fn () => $ database ->deleteCollection ($ collection ));
1677+ $ auth ->skip (fn () => $ database ->deleteCollection ($ collection ));
1678+ }
16771679 }
16781680
16791681 public function testIncreaseDecrease (): Document
0 commit comments