Files
api/web/__test__/authorizationScopes.test.ts
Eli Bosley af5ca11860 Feat/vue (#1655)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Introduced Docker management UI components: Overview, Logs, Console,
Preview, and Edit.
- Added responsive Card/Detail layouts with grouping, bulk actions, and
tabs.
  - New UnraidToaster component and global toaster configuration.
- Component auto-mounting improved with async loading and multi-selector
support.
- UI/UX
- Overhauled theme system (light/dark tokens, primary/orange accents)
and added theme variants.
  - Header OS version now includes integrated changelog modal.
- Registration displays warning states; multiple visual polish updates.
- API
  - CPU load now includes percentGuest and percentSteal metrics.
- Chores
  - Migrated web app to Vite; updated artifacts and manifests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mdatelle <mike@datelle.net>
Co-authored-by: Michael Datelle <mdatelle@icloud.com>
2025-09-08 10:04:49 -04:00

213 lines
7.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { AuthAction, Resource } from '~/composables/gql/graphql';
import { decodeScopesToPermissions, encodePermissionsToScopes } from '~/utils/authorizationScopes';
describe('authorizationScopes', () => {
describe('encodePermissionsToScopes', () => {
describe('duplicate handling', () => {
it('should deduplicate action verbs when multiple permissions have duplicate actions', () => {
const permissions = [
{
resource: Resource.DOCKER,
actions: [AuthAction.READ_ANY, AuthAction.READ_OWN, AuthAction.UPDATE_ANY],
},
];
const scopes = encodePermissionsToScopes([], permissions);
// Should produce "docker:read_any+read_own+update_any" with distinct actions preserved
expect(scopes).toHaveLength(1);
expect(scopes[0]).toBe('docker:read_any+read_own+update_any');
});
it('should deduplicate resource names when grouped', () => {
const permissions = [
{
resource: Resource.DOCKER,
actions: [AuthAction.READ_ANY],
},
{
resource: Resource.DOCKER,
actions: [AuthAction.READ_OWN],
},
];
const scopes = encodePermissionsToScopes([], permissions);
// Should produce "docker:read_any+read_own" merging both permissions
expect(scopes).toHaveLength(1);
expect(scopes[0]).toBe('docker:read_any+read_own');
});
it('should handle multiple duplicate resources and actions correctly', () => {
const permissions = [
{
resource: Resource.DOCKER,
actions: [AuthAction.READ_ANY, AuthAction.READ_OWN, AuthAction.UPDATE_ANY],
},
{
resource: Resource.VMS,
actions: [AuthAction.READ_ANY, AuthAction.UPDATE_OWN, AuthAction.UPDATE_ANY],
},
{
resource: Resource.DOCKER,
actions: [AuthAction.READ_OWN, AuthAction.UPDATE_OWN],
},
];
const scopes = encodePermissionsToScopes([], permissions);
// Docker: READ_ANY, READ_OWN, UPDATE_ANY, UPDATE_OWN (merged from both)
// VMS: READ_ANY, UPDATE_OWN, UPDATE_ANY
// Different action sets, so separate scopes
expect(scopes).toHaveLength(2);
const scopeStrings = scopes.sort();
expect(scopeStrings).toContain('docker:read_any+read_own+update_any+update_own');
expect(scopeStrings).toContain('vms:read_any+update_any+update_own');
});
it('should maintain consistent sorting for actions and resources', () => {
const permissions = [
{
resource: Resource.VMS,
actions: [AuthAction.UPDATE_ANY, AuthAction.READ_ANY],
},
{
resource: Resource.DOCKER,
actions: [AuthAction.UPDATE_ANY, AuthAction.READ_ANY],
},
];
const scopes = encodePermissionsToScopes([], permissions);
// Should sort resources (docker before vms) and actions alphabetically
expect(scopes).toHaveLength(1);
expect(scopes[0]).toBe('docker+vms:read_any+update_any');
});
it('should handle all CRUD actions without creating wildcard', () => {
const permissions = [
{
resource: Resource.DOCKER,
actions: [
AuthAction.CREATE_ANY,
AuthAction.CREATE_OWN,
AuthAction.READ_ANY,
AuthAction.READ_OWN,
AuthAction.UPDATE_ANY,
AuthAction.UPDATE_OWN,
AuthAction.DELETE_ANY,
AuthAction.DELETE_OWN,
],
},
];
const scopes = encodePermissionsToScopes([], permissions);
// Should just list all actions, no wildcard conversion
expect(scopes).toHaveLength(1);
expect(scopes[0]).toBe(
'docker:create_any+create_own+delete_any+delete_own+read_any+read_own+update_any+update_own'
);
});
it('should handle edge case with empty actions after deduplication', () => {
const permissions = [
{
resource: Resource.DOCKER,
actions: [],
},
];
const scopes = encodePermissionsToScopes([], permissions);
// Should not produce any scope for empty actions
expect(scopes).toHaveLength(0);
});
it('should deduplicate resources across multiple permissions with same action set', () => {
const permissions = [
{
resource: Resource.DOCKER,
actions: [AuthAction.READ_ANY],
},
{
resource: Resource.VMS,
actions: [AuthAction.READ_OWN],
},
{
resource: Resource.DOCKER,
actions: [AuthAction.READ_OWN],
},
{
resource: Resource.VMS,
actions: [AuthAction.READ_ANY],
},
];
const scopes = encodePermissionsToScopes([], permissions);
// Both DOCKER and VMS have READ_ANY and READ_OWN, should group without duplicates
expect(scopes).toHaveLength(1);
expect(scopes[0]).toBe('docker+vms:read_any+read_own');
});
it('should produce deterministic output for same input regardless of order', () => {
const permissions1 = [
{ resource: Resource.VMS, actions: [AuthAction.UPDATE_ANY, AuthAction.READ_ANY] },
{ resource: Resource.DOCKER, actions: [AuthAction.READ_ANY, AuthAction.UPDATE_ANY] },
];
const permissions2 = [
{ resource: Resource.DOCKER, actions: [AuthAction.UPDATE_ANY, AuthAction.READ_ANY] },
{ resource: Resource.VMS, actions: [AuthAction.READ_ANY, AuthAction.UPDATE_ANY] },
];
const scopes1 = encodePermissionsToScopes([], permissions1);
const scopes2 = encodePermissionsToScopes([], permissions2);
expect(scopes1).toEqual(scopes2);
expect(scopes1[0]).toBe('docker+vms:read_any+update_any');
});
});
describe('roundtrip encoding/decoding', () => {
it('should maintain permissions through encode/decode cycle with duplicates', () => {
const originalPermissions = [
{
resource: Resource.DOCKER,
actions: [AuthAction.READ_ANY, AuthAction.READ_OWN, AuthAction.UPDATE_ANY],
},
{
resource: Resource.VMS,
actions: [AuthAction.READ_OWN, AuthAction.UPDATE_OWN, AuthAction.UPDATE_ANY],
},
];
const scopes = encodePermissionsToScopes([], originalPermissions);
const { permissions: decoded } = decodeScopesToPermissions(scopes);
// Now possession is preserved in the encoding
expect(decoded).toHaveLength(2);
const dockerPerm = decoded.find((p) => p.resource === Resource.DOCKER);
const vmsPerm = decoded.find((p) => p.resource === Resource.VMS);
// Docker should have its specific actions preserved
expect(dockerPerm?.actions).toContain(AuthAction.READ_ANY);
expect(dockerPerm?.actions).toContain(AuthAction.READ_OWN);
expect(dockerPerm?.actions).toContain(AuthAction.UPDATE_ANY);
// VMS should have its specific actions preserved
expect(vmsPerm?.actions).toContain(AuthAction.READ_OWN);
expect(vmsPerm?.actions).toContain(AuthAction.UPDATE_OWN);
expect(vmsPerm?.actions).toContain(AuthAction.UPDATE_ANY);
// The scopes should be separate since they have different action sets
expect(scopes).toHaveLength(2);
});
});
});
});