@@ -110,13 +110,37 @@ private function shape(array $rows): array
110110 return $ shape ;
111111 }
112112
113- public function testColumnIsPersistedOnThePermissionsTable (): void
113+ /**
114+ * A column-scoped grant lives in two places: the _permissions JSON on the row, which
115+ * drives masking, and a _perms row, which drives the find/count/sum gate. Re-scoping
116+ * the grant to another column has to move both. If only the JSON is rewritten the
117+ * filter still answers on the old column -- which is what happens when the permission
118+ * diff compares roles and ignores the column.
119+ */
120+ public function testRescopingAGrantMovesBothTheMaskAndTheFilter (): void
114121 {
115- $ rows = $ this ->authorization ->skip (
116- fn () => $ this ->database ->find ('employees ' , [Query::equal ('$id ' , ['e1 ' ])])
122+ $ this ->as (['any ' , 'user:hr ' ]);
123+
124+ $ this ->assertSame (
125+ ['e1 ' => ['name ' , 'salary ' ]],
126+ $ this ->shape ($ this ->database ->find ('employees ' , [Query::greaterThan ('salary ' , 95000 )]))
127+ );
128+
129+ $ this ->authorization ->skip (fn () => $ this ->database ->updateDocument ('employees ' , 'e1 ' , new Document ([
130+ '$permissions ' => [Permission::read (Role::user ('hr ' ), 'name ' )],
131+ ])));
132+
133+ $ this ->as (['any ' , 'user:hr ' ]);
134+
135+ // the mask no longer yields salary...
136+ $ this ->assertSame (
137+ ['e1 ' => ['name ' ], 'e2 ' => ['name ' ]],
138+ $ this ->shape ($ this ->database ->find ('employees ' ))
117139 );
118140
119- $ this ->assertSame (['read("user:hr", "salary") ' ], $ rows [0 ]->getPermissions ());
141+ // ...and neither does the gate, so the row cannot be found through it
142+ $ this ->assertSame ([], $ this ->database ->find ('employees ' , [Query::greaterThan ('salary ' , 95000 )]));
143+ $ this ->assertSame (0 , $ this ->database ->sum ('employees ' , 'salary ' ));
120144 }
121145
122146 /**
0 commit comments