permission : $permission; return $this->hasAllPermission && $permissionKey === 'list_xlvask_usage_orders_all'; } } class XLVaskUsageHallScopeDbFake { public int $queryCount = 0; private bool $softDeleteQueryAttempted = false; /** @param array $rows */ public function __construct( private readonly array $rows, private readonly bool $deletedAtColumnMissing = false ) { } public function query(string $sql): object { $this->queryCount++; if ($this->deletedAtColumnMissing && !$this->softDeleteQueryAttempted && str_contains($sql, 'deleted_at IS NULL')) { $this->softDeleteQueryAttempted = true; throw new \Exception("Unknown column 'deleted_at' in 'WHERE'"); } expect($sql)->toContain('SELECT DISTINCT HallId FROM plate_scanners'); return new stdClass(); } /** @return array */ public function fetch_all(object $result): array { return $this->rows; } } /** * @param array $groupHallIds */ function xlvask_usage_hall_scope_user(array $groupHallIds): object { return new class($groupHallIds) { /** @param array $groupHallIds */ public function __construct(private readonly array $groupHallIds) { } public function getGroup(): object { return new class($this->groupHallIds) { /** @param array $groupHallIds */ public function __construct(private readonly array $groupHallIds) { } /** @return array */ public function getDepartmentsScannersHallIds(): array { return $this->groupHallIds; } }; } }; } /** @param array $groupHallIds */ function resolve_xlvask_usage_hall_scope( XLVaskUsageHallScopeRouteHarness $route, XLVaskUsageHallScopeDbFake $dbFake, array $groupHallIds ): array { $hadDb = array_key_exists('db', $GLOBALS); $originalDb = $GLOBALS['db'] ?? null; $GLOBALS['db'] = $dbFake; try { $method = new ReflectionMethod(\routes\xlvaskUsageLogsRoute::class, 'allowedHallIdsForUser'); return $method->invoke($route, xlvask_usage_hall_scope_user($groupHallIds)); } finally { if ($hadDb) { $GLOBALS['db'] = $originalDb; } else { unset($GLOBALS['db']); } } } it('uses every normalized scanner hall for users with all-scope permission', function (): void { $route = new XLVaskUsageHallScopeRouteHarness(); $route->hasAllPermission = true; $dbFake = new XLVaskUsageHallScopeDbFake([ ['HallId' => ' Hall-A '], ['HallId' => ''], ['HallId' => 'Hall-A'], ['HallId' => str_repeat('x', 192)], ['HallId' => 'Hall-B'], ]); $hallIds = resolve_xlvask_usage_hall_scope($route, $dbFake, ['Own-Hall']); expect($hallIds)->toBe(['Hall-A', 'Hall-B']) ->and($dbFake->queryCount)->toBe(1); }); it('falls back when plate scanners table has no deleted_at column', function (): void { $route = new XLVaskUsageHallScopeRouteHarness(); $route->hasAllPermission = true; $dbFake = new XLVaskUsageHallScopeDbFake([ ['HallId' => ' Hall-A '], ['HallId' => 'Hall-B'], ], true); $hallIds = resolve_xlvask_usage_hall_scope($route, $dbFake, ['Own-Hall']); expect($hallIds)->toBe(['Hall-A', 'Hall-B']) ->and($dbFake->queryCount)->toBe(2); }); it('keeps own-scope users limited to normalized group halls', function (): void { $route = new XLVaskUsageHallScopeRouteHarness(); $dbFake = new XLVaskUsageHallScopeDbFake([ ['HallId' => 'Global-Hall'], ]); $hallIds = resolve_xlvask_usage_hall_scope($route, $dbFake, [ ' Own-Hall-A ', '', 'Own-Hall-A', 'Own-Hall-B', ]); expect($hallIds)->toBe(['Own-Hall-A', 'Own-Hall-B']) ->and($dbFake->queryCount)->toBe(0); });