@@ -245,6 +245,147 @@ public function testSaveUserPreferenceForUserIdPersistsForTargetUser(): void {
245245 $ this ->assertSame ('user ' , $ resolved ->getSourceScope ());
246246 }
247247
248+ public function testSaveUserPreferenceForUserIdAllowsSystemAdminBypassWhenGroupBlocksUsers (): void {
249+ $ targetUser = $ this ->createMock (IUser::class);
250+ $ targetUser ->method ('getUID ' )->willReturn ('user1 ' );
251+
252+ $ actor = $ this ->createMock (IUser::class);
253+ $ actor ->method ('getUID ' )->willReturn ('admin ' );
254+ $ this ->userSession
255+ ->method ('getUser ' )
256+ ->willReturn ($ actor );
257+
258+ $ this ->userManager
259+ ->expects ($ this ->once ())
260+ ->method ('get ' )
261+ ->with ('user1 ' )
262+ ->willReturn ($ targetUser );
263+
264+ $ this ->groupManager
265+ ->expects ($ this ->once ())
266+ ->method ('getUserGroupIds ' )
267+ ->with ($ targetUser )
268+ ->willReturn (['finance ' ]);
269+
270+ $ this ->groupManager
271+ ->expects ($ this ->once ())
272+ ->method ('isAdmin ' )
273+ ->with ('admin ' )
274+ ->willReturn (true );
275+
276+ $ this ->source
277+ ->expects ($ this ->once ())
278+ ->method ('saveUserPreference ' )
279+ ->with (
280+ SignatureFlowPolicy::KEY ,
281+ $ this ->callback (static function ($ context ): bool {
282+ return $ context ->getUserId () === 'user1 ' ;
283+ }),
284+ 'ordered_numeric ' ,
285+ );
286+
287+ $ this ->source
288+ ->method ('loadSystemPolicy ' )
289+ ->willReturn ((new PolicyLayer ())
290+ ->setScope ('system ' )
291+ ->setValue ('none ' )
292+ ->setAllowChildOverride (true )
293+ ->setVisibleToChild (true ));
294+
295+ $ this ->source
296+ ->method ('loadGroupPolicies ' )
297+ ->willReturn ([(new PolicyLayer ())
298+ ->setScope ('group ' )
299+ ->setValue ('ordered_numeric ' )
300+ ->setAllowChildOverride (false )
301+ ->setVisibleToChild (true )
302+ ->setAllowedValues (['ordered_numeric ' ])]);
303+
304+ $ this ->source ->method ('loadCirclePolicies ' )->willReturn ([]);
305+ $ this ->source
306+ ->method ('loadUserPreference ' )
307+ ->willReturn ((new PolicyLayer ())
308+ ->setScope ('user ' )
309+ ->setValue ('ordered_numeric ' ));
310+ $ this ->source ->method ('loadRequestOverride ' )->willReturn (null );
311+
312+ $ service = new PolicyService (
313+ $ this ->contextFactory ,
314+ $ this ->source ,
315+ $ this ->registry ,
316+ );
317+
318+ $ resolved = $ service ->saveUserPreferenceForUserId (SignatureFlowPolicy::KEY , 'user1 ' , 'ordered_numeric ' );
319+
320+ $ this ->assertSame ('ordered_numeric ' , $ resolved ->getEffectiveValue ());
321+ $ this ->assertSame ('group ' , $ resolved ->getSourceScope ());
322+ }
323+
324+ public function testSaveUserPreferenceForUserIdBlocksNonAdminWhenGroupDisallowsUserOverrides (): void {
325+ $ targetUser = $ this ->createMock (IUser::class);
326+ $ targetUser ->method ('getUID ' )->willReturn ('user1 ' );
327+
328+ $ actor = $ this ->createMock (IUser::class);
329+ $ actor ->method ('getUID ' )->willReturn ('manager ' );
330+ $ this ->userSession
331+ ->method ('getUser ' )
332+ ->willReturn ($ actor );
333+
334+ $ this ->userManager
335+ ->expects ($ this ->once ())
336+ ->method ('get ' )
337+ ->with ('user1 ' )
338+ ->willReturn ($ targetUser );
339+
340+ $ this ->groupManager
341+ ->expects ($ this ->once ())
342+ ->method ('getUserGroupIds ' )
343+ ->with ($ targetUser )
344+ ->willReturn (['finance ' ]);
345+
346+ $ this ->groupManager
347+ ->expects ($ this ->once ())
348+ ->method ('isAdmin ' )
349+ ->with ('manager ' )
350+ ->willReturn (false );
351+
352+ $ this ->source
353+ ->expects ($ this ->never ())
354+ ->method ('saveUserPreference ' );
355+
356+ $ this ->source
357+ ->method ('loadSystemPolicy ' )
358+ ->willReturn ((new PolicyLayer ())
359+ ->setScope ('system ' )
360+ ->setValue ('none ' )
361+ ->setAllowChildOverride (true )
362+ ->setVisibleToChild (true ));
363+
364+ $ this ->source
365+ ->method ('loadGroupPolicies ' )
366+ ->willReturn ([(new PolicyLayer ())
367+ ->setScope ('group ' )
368+ ->setValue ('ordered_numeric ' )
369+ ->setAllowChildOverride (false )
370+ ->setVisibleToChild (true )
371+ ->setAllowedValues (['ordered_numeric ' ])]);
372+
373+ $ this ->source ->method ('loadCirclePolicies ' )->willReturn ([]);
374+ $ this ->source ->method ('loadUserPreference ' )->willReturn (null );
375+ $ this ->source ->method ('loadRequestOverride ' )->willReturn (null );
376+
377+ $ service = new PolicyService (
378+ $ this ->contextFactory ,
379+ $ this ->source ,
380+ $ this ->registry ,
381+ );
382+
383+ $ this ->expectException (\InvalidArgumentException::class);
384+ $ this ->expectExceptionMessage ('Saving a user preference is not allowed for signature_flow ' );
385+
386+ $ service ->saveUserPreferenceForUserId (SignatureFlowPolicy::KEY , 'user1 ' , 'ordered_numeric ' );
387+ }
388+
248389 public function testSaveSystemPersistsAllowChildOverrideWhenEnabled (): void {
249390 $ this ->source
250391 ->expects ($ this ->once ())
0 commit comments