toArray())->toBe(['read', 'write', 'admin']); expect($scope->toString())->toBe('read write admin'); }); it('creates from space-separated string', function () { $scope = TokenScope::fromString('read write admin'); expect($scope->toArray())->toBe(['read', 'write', 'admin']); }); it('handles extra whitespace in string', function () { $scope = TokenScope::fromString(' read write admin '); expect($scope->toArray())->toBe(['read', 'write', 'admin']); }); it('rejects empty scope array', function () { TokenScope::fromArray([]); })->throws(\InvalidArgumentException::class, 'Token scope cannot be empty'); it('rejects empty scope string', function () { TokenScope::fromString(''); })->throws(\InvalidArgumentException::class, 'Token scope cannot be empty'); it('rejects scope array with empty strings', function () { TokenScope::fromArray(['read', '', 'write']); })->throws(\InvalidArgumentException::class, 'Invalid scope value'); it('rejects scope array with non-strings', function () { TokenScope::fromArray(['read', 123, 'write']); })->throws(\InvalidArgumentException::class, 'Invalid scope value'); it('checks if scope includes specific permission', function () { $scope = TokenScope::fromArray(['read', 'write', 'admin']); expect($scope->includes('read'))->toBeTrue(); expect($scope->includes('write'))->toBeTrue(); expect($scope->includes('admin'))->toBeTrue(); expect($scope->includes('delete'))->toBeFalse(); }); it('checks if scope includes all required permissions', function () { $scope = TokenScope::fromArray(['read', 'write', 'admin']); expect($scope->includesAll(['read', 'write']))->toBeTrue(); expect($scope->includesAll(['read', 'admin']))->toBeTrue(); expect($scope->includesAll(['read', 'write', 'admin']))->toBeTrue(); expect($scope->includesAll(['read', 'delete']))->toBeFalse(); }); it('checks if scope includes any of specified permissions', function () { $scope = TokenScope::fromArray(['read', 'write']); expect($scope->includesAny(['read', 'admin']))->toBeTrue(); expect($scope->includesAny(['write', 'delete']))->toBeTrue(); expect($scope->includesAny(['admin', 'delete']))->toBeFalse(); }); it('adds additional scopes immutably', function () { $originalScope = TokenScope::fromArray(['read', 'write']); $extendedScope = $originalScope->withAdditional(['admin', 'delete']); // Original unchanged expect($originalScope->toArray())->toBe(['read', 'write']); // New instance extended expect($extendedScope->toArray())->toBe(['read', 'write', 'admin', 'delete']); }); it('removes specific scopes immutably', function () { $originalScope = TokenScope::fromArray(['read', 'write', 'admin', 'delete']); $reducedScope = $originalScope->without(['admin', 'delete']); // Original unchanged expect($originalScope->toArray())->toBe(['read', 'write', 'admin', 'delete']); // New instance reduced expect($reducedScope->toArray())->toBe(['read', 'write']); }); it('prevents removing all scopes', function () { $scope = TokenScope::fromArray(['read', 'write']); $scope->without(['read', 'write']); })->throws(\InvalidArgumentException::class, 'Cannot remove all scopes'); it('converts to string correctly', function () { $scope = TokenScope::fromArray(['read', 'write', 'admin']); expect($scope->toString())->toBe('read write admin'); expect((string) $scope)->toBe('read write admin'); }); });