@nextsparkjs/theme-default 0.1.0-beta.24 → 0.1.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/tests/jest/__mocks__/@nextsparkjs/core/components/ui/badge.js +16 -0
- package/tests/jest/__mocks__/@nextsparkjs/core/lib/db.js +11 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +155 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/theme-registry.ts +68 -0
- package/tests/jest/__mocks__/next/image.js +15 -0
- package/tests/jest/jest.config.cjs +15 -4
- package/tests/jest/langchain/streaming.test.ts +6 -3
- package/tests/jest/tsconfig.jest.json +6 -0
- package/tests/jest/config/role-config.test.ts +0 -529
- package/tests/jest/user-roles/role-helpers.test.ts +0 -432
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for @nextsparkjs/core/components/ui/badge
|
|
3
|
+
*/
|
|
4
|
+
const React = require('react')
|
|
5
|
+
|
|
6
|
+
const Badge = ({ children, variant = 'default', className = '', ...props }) => {
|
|
7
|
+
return React.createElement('span', {
|
|
8
|
+
className: `badge badge-${variant} ${className}`.trim(),
|
|
9
|
+
'data-testid': 'badge',
|
|
10
|
+
...props,
|
|
11
|
+
}, children)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = { Badge }
|
|
15
|
+
module.exports.Badge = Badge
|
|
16
|
+
module.exports.default = Badge
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Permissions Registry for Jest tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type Permission = string
|
|
6
|
+
|
|
7
|
+
export interface ResolvedPermission {
|
|
8
|
+
id: string
|
|
9
|
+
label: string
|
|
10
|
+
description: string
|
|
11
|
+
category: string
|
|
12
|
+
roles: string[]
|
|
13
|
+
dangerous: boolean
|
|
14
|
+
source: 'core' | 'theme'
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PermissionUISection {
|
|
19
|
+
id: string
|
|
20
|
+
label: string
|
|
21
|
+
description: string
|
|
22
|
+
categories: string[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RolesConfig {
|
|
26
|
+
additionalRoles?: string[]
|
|
27
|
+
hierarchy?: Record<string, number>
|
|
28
|
+
displayNames?: Record<string, string>
|
|
29
|
+
descriptions?: Record<string, string>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Mock permissions data
|
|
33
|
+
export const ALL_RESOLVED_PERMISSIONS: ResolvedPermission[] = [
|
|
34
|
+
{
|
|
35
|
+
id: 'tasks.create',
|
|
36
|
+
label: 'Create tasks',
|
|
37
|
+
description: 'Can create new tasks',
|
|
38
|
+
category: 'Tasks',
|
|
39
|
+
roles: ['owner', 'admin', 'member'],
|
|
40
|
+
dangerous: false,
|
|
41
|
+
source: 'theme',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'tasks.read',
|
|
45
|
+
label: 'View tasks',
|
|
46
|
+
description: 'Can view task details',
|
|
47
|
+
category: 'Tasks',
|
|
48
|
+
roles: ['owner', 'admin', 'member'],
|
|
49
|
+
dangerous: false,
|
|
50
|
+
source: 'theme',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'tasks.update',
|
|
54
|
+
label: 'Edit tasks',
|
|
55
|
+
description: 'Can modify task information',
|
|
56
|
+
category: 'Tasks',
|
|
57
|
+
roles: ['owner', 'admin', 'member'],
|
|
58
|
+
dangerous: false,
|
|
59
|
+
source: 'theme',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'tasks.delete',
|
|
63
|
+
label: 'Delete tasks',
|
|
64
|
+
description: 'Can delete tasks',
|
|
65
|
+
category: 'Tasks',
|
|
66
|
+
roles: ['owner', 'admin'],
|
|
67
|
+
dangerous: true,
|
|
68
|
+
source: 'theme',
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export const ALL_PERMISSIONS: Permission[] = ALL_RESOLVED_PERMISSIONS.map(p => p.id)
|
|
73
|
+
|
|
74
|
+
export const ALL_PERMISSIONS_SET = new Set(ALL_PERMISSIONS)
|
|
75
|
+
|
|
76
|
+
export const AVAILABLE_ROLES: readonly string[] = ['owner', 'admin', 'member', 'viewer']
|
|
77
|
+
|
|
78
|
+
export const ROLE_HIERARCHY: Record<string, number> = {
|
|
79
|
+
owner: 100,
|
|
80
|
+
admin: 50,
|
|
81
|
+
member: 10,
|
|
82
|
+
viewer: 1,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const ROLE_DISPLAY_NAMES: Record<string, string> = {
|
|
86
|
+
owner: 'common.teamRoles.owner',
|
|
87
|
+
admin: 'common.teamRoles.admin',
|
|
88
|
+
member: 'common.teamRoles.member',
|
|
89
|
+
viewer: 'common.teamRoles.viewer',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const ROLE_DESCRIPTIONS: Record<string, string> = {
|
|
93
|
+
owner: 'Full team control, cannot be removed',
|
|
94
|
+
admin: 'Manage team members and settings',
|
|
95
|
+
member: 'Standard team access',
|
|
96
|
+
viewer: 'Read-only access to team resources',
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const CUSTOM_ROLES: RolesConfig = {
|
|
100
|
+
additionalRoles: [],
|
|
101
|
+
hierarchy: {},
|
|
102
|
+
displayNames: {},
|
|
103
|
+
descriptions: {},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const PERMISSIONS_BY_ROLE: Record<string, Set<Permission>> = {
|
|
107
|
+
owner: new Set(ALL_PERMISSIONS),
|
|
108
|
+
admin: new Set(['tasks.create', 'tasks.read', 'tasks.update', 'tasks.delete']),
|
|
109
|
+
member: new Set(['tasks.create', 'tasks.read', 'tasks.update']),
|
|
110
|
+
viewer: new Set(['tasks.read']),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const ROLE_PERMISSIONS_ARRAY: Record<string, Permission[]> = {
|
|
114
|
+
owner: [...ALL_PERMISSIONS],
|
|
115
|
+
admin: ['tasks.create', 'tasks.read', 'tasks.update', 'tasks.delete'],
|
|
116
|
+
member: ['tasks.create', 'tasks.read', 'tasks.update'],
|
|
117
|
+
viewer: ['tasks.read'],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const PERMISSIONS_BY_CATEGORY: Record<string, ResolvedPermission[]> = {
|
|
121
|
+
Tasks: ALL_RESOLVED_PERMISSIONS,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const FULL_MATRIX: Record<Permission, Record<string, boolean>> = {}
|
|
125
|
+
for (const perm of ALL_RESOLVED_PERMISSIONS) {
|
|
126
|
+
FULL_MATRIX[perm.id] = {}
|
|
127
|
+
for (const role of AVAILABLE_ROLES) {
|
|
128
|
+
FULL_MATRIX[perm.id][role] = PERMISSIONS_BY_ROLE[role]?.has(perm.id) ?? false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const UI_SECTIONS: PermissionUISection[] = [
|
|
133
|
+
{
|
|
134
|
+
id: 'entities',
|
|
135
|
+
label: 'Entities',
|
|
136
|
+
description: 'Entity-specific permissions',
|
|
137
|
+
categories: ['Tasks'],
|
|
138
|
+
},
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
export const TEAM_PERMISSIONS_RAW = []
|
|
142
|
+
export const TEAM_PERMISSIONS_BY_ROLE: Record<string, string[]> = {}
|
|
143
|
+
|
|
144
|
+
export const PERMISSIONS_METADATA = {
|
|
145
|
+
totalPermissions: ALL_PERMISSIONS.length,
|
|
146
|
+
corePermissions: 0,
|
|
147
|
+
teamPermissions: 0,
|
|
148
|
+
featurePermissions: 0,
|
|
149
|
+
entityPermissions: ALL_RESOLVED_PERMISSIONS.length,
|
|
150
|
+
customRoles: 0,
|
|
151
|
+
availableRoles: AVAILABLE_ROLES.length,
|
|
152
|
+
categories: Object.keys(PERMISSIONS_BY_CATEGORY).length,
|
|
153
|
+
generatedAt: new Date().toISOString(),
|
|
154
|
+
theme: 'default',
|
|
155
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Theme Registry for Jest tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ThemeEntity {
|
|
6
|
+
name: string
|
|
7
|
+
source: string
|
|
8
|
+
config: any
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ThemeRouteFile {
|
|
12
|
+
path: string
|
|
13
|
+
type: 'api' | 'page'
|
|
14
|
+
methods?: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ThemeRegistryEntry {
|
|
18
|
+
name: string
|
|
19
|
+
slug: string
|
|
20
|
+
displayName: string
|
|
21
|
+
description: string
|
|
22
|
+
version: string
|
|
23
|
+
entities: ThemeEntity[]
|
|
24
|
+
routes: ThemeRouteFile[]
|
|
25
|
+
middlewares: any[]
|
|
26
|
+
plugins: string[]
|
|
27
|
+
path: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const THEME_REGISTRY: Record<string, ThemeRegistryEntry> = {
|
|
31
|
+
default: {
|
|
32
|
+
name: 'default',
|
|
33
|
+
slug: 'default',
|
|
34
|
+
displayName: 'Default Theme',
|
|
35
|
+
description: 'Default theme for NextSpark',
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
entities: [
|
|
38
|
+
{ name: 'posts', source: 'default', config: { name: 'posts', label: 'Posts' } }
|
|
39
|
+
],
|
|
40
|
+
routes: [
|
|
41
|
+
{ path: '/api/v1/posts', type: 'api', methods: ['GET', 'POST'] }
|
|
42
|
+
],
|
|
43
|
+
middlewares: [],
|
|
44
|
+
plugins: [],
|
|
45
|
+
path: 'contents/themes/default',
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type ThemeName = keyof typeof THEME_REGISTRY | string
|
|
50
|
+
|
|
51
|
+
export const THEME_METADATA = {
|
|
52
|
+
generated: new Date().toISOString(),
|
|
53
|
+
activeTheme: 'default',
|
|
54
|
+
totalThemes: Object.keys(THEME_REGISTRY).length,
|
|
55
|
+
totalThemeEntities: 1,
|
|
56
|
+
totalThemeRoutes: 1,
|
|
57
|
+
themes: Object.keys(THEME_REGISTRY),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Helper function for getting active theme
|
|
61
|
+
export function getActiveTheme(): ThemeRegistryEntry | undefined {
|
|
62
|
+
return THEME_REGISTRY[THEME_METADATA.activeTheme]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Helper function for getting theme by name
|
|
66
|
+
export function getTheme(name: string): ThemeRegistryEntry | undefined {
|
|
67
|
+
return THEME_REGISTRY[name]
|
|
68
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for next/image
|
|
3
|
+
*/
|
|
4
|
+
const React = require('react')
|
|
5
|
+
|
|
6
|
+
const Image = ({ src, alt, ...props }) => {
|
|
7
|
+
return React.createElement('img', {
|
|
8
|
+
src: typeof src === 'object' ? src.src : src,
|
|
9
|
+
alt: alt || '',
|
|
10
|
+
...props,
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = Image
|
|
15
|
+
module.exports.default = Image
|
|
@@ -27,9 +27,18 @@ const projectRoot = isNpmMode ? npmModeRoot : monorepoAppRoot
|
|
|
27
27
|
// Module name mapper based on mode
|
|
28
28
|
const moduleNameMapper = isNpmMode
|
|
29
29
|
? {
|
|
30
|
-
// NPM mode:
|
|
31
|
-
'^@nextsparkjs/core/
|
|
32
|
-
|
|
30
|
+
// NPM mode: Mock core UI components (ESM can't be transformed by Jest)
|
|
31
|
+
'^@nextsparkjs/core/components/ui/badge$': path.join(themeTestsRoot, '__mocks__/@nextsparkjs/core/components/ui/badge.js'),
|
|
32
|
+
// NPM mode: Mock core lib modules that are ESM
|
|
33
|
+
'^@nextsparkjs/core/lib/db$': path.join(themeTestsRoot, '__mocks__/@nextsparkjs/core/lib/db.js'),
|
|
34
|
+
// NPM mode: explicitly resolve @nextsparkjs/core subpaths to dist directory
|
|
35
|
+
// Jest doesn't respect package.json exports, so we map directly to dist files
|
|
36
|
+
'^@nextsparkjs/core/lib/(.*)$': '<rootDir>/node_modules/@nextsparkjs/core/dist/lib/$1',
|
|
37
|
+
'^@nextsparkjs/core/hooks/(.*)$': '<rootDir>/node_modules/@nextsparkjs/core/dist/hooks/$1',
|
|
38
|
+
'^@nextsparkjs/core/components/(.*)$': '<rootDir>/node_modules/@nextsparkjs/core/dist/components/$1',
|
|
39
|
+
'^@nextsparkjs/core/(.*)$': '<rootDir>/node_modules/@nextsparkjs/core/dist/$1',
|
|
40
|
+
'^@nextsparkjs/core$': '<rootDir>/node_modules/@nextsparkjs/core/dist/index.js',
|
|
41
|
+
'^@nextsparkjs/registries/(.*)$': path.join(themeTestsRoot, '__mocks__/@nextsparkjs/registries/$1'),
|
|
33
42
|
'^@/contents/(.*)$': '<rootDir>/contents/$1',
|
|
34
43
|
'^@/entities/(.*)$': '<rootDir>/contents/entities/$1',
|
|
35
44
|
'^@/plugins/(.*)$': '<rootDir>/contents/plugins/$1',
|
|
@@ -37,6 +46,7 @@ const moduleNameMapper = isNpmMode
|
|
|
37
46
|
'^@/(.*)$': '<rootDir>/$1',
|
|
38
47
|
// Mocks from theme-local folder
|
|
39
48
|
'next/server': path.join(themeTestsRoot, '__mocks__/next-server.js'),
|
|
49
|
+
'next/image': path.join(themeTestsRoot, '__mocks__/next/image.js'),
|
|
40
50
|
'^jose$': path.join(themeTestsRoot, '__mocks__/jose.js'),
|
|
41
51
|
'^jose/(.*)$': path.join(themeTestsRoot, '__mocks__/jose.js'),
|
|
42
52
|
}
|
|
@@ -44,6 +54,7 @@ const moduleNameMapper = isNpmMode
|
|
|
44
54
|
// Monorepo mode: resolve from packages/core/src (rootDir is apps/dev)
|
|
45
55
|
'^@nextsparkjs/core/(.*)$': '<rootDir>/../../packages/core/src/$1',
|
|
46
56
|
'^@nextsparkjs/core$': '<rootDir>/../../packages/core/src',
|
|
57
|
+
'^@nextsparkjs/registries/(.*)$': '<rootDir>/../../packages/core/tests/jest/__mocks__/@nextsparkjs/registries/$1',
|
|
47
58
|
'^@/contents/(.*)$': '<rootDir>/contents/$1',
|
|
48
59
|
'^@/entities/(.*)$': '<rootDir>/contents/entities/$1',
|
|
49
60
|
'^@/plugins/(.*)$': '<rootDir>/contents/plugins/$1',
|
|
@@ -96,7 +107,7 @@ module.exports = {
|
|
|
96
107
|
// Transform configuration
|
|
97
108
|
transform: {
|
|
98
109
|
'^.+\\.(ts|tsx)$': ['ts-jest', {
|
|
99
|
-
tsconfig: path.join(
|
|
110
|
+
tsconfig: path.join(themeTestsRoot, 'tsconfig.jest.json'),
|
|
100
111
|
}],
|
|
101
112
|
},
|
|
102
113
|
|
|
@@ -127,7 +127,8 @@ describe('Streaming Service', () => {
|
|
|
127
127
|
const chunk: StreamChunk = { type: 'token', content: 'test' }
|
|
128
128
|
const result = encoder.encode(chunk)
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
// Use constructor name check to avoid jsdom cross-realm issues
|
|
131
|
+
expect(result.constructor.name).toBe('Uint8Array')
|
|
131
132
|
})
|
|
132
133
|
})
|
|
133
134
|
|
|
@@ -142,7 +143,8 @@ describe('Streaming Service', () => {
|
|
|
142
143
|
it('should return Uint8Array', () => {
|
|
143
144
|
const result = encoder.encodeDone()
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
// Use constructor name check to avoid jsdom cross-realm issues
|
|
147
|
+
expect(result.constructor.name).toBe('Uint8Array')
|
|
146
148
|
})
|
|
147
149
|
})
|
|
148
150
|
})
|
|
@@ -360,7 +362,8 @@ describe('Streaming Service', () => {
|
|
|
360
362
|
|
|
361
363
|
expect(results).toHaveLength(100)
|
|
362
364
|
results.forEach(result => {
|
|
363
|
-
|
|
365
|
+
// Use constructor name check to avoid jsdom cross-realm issues
|
|
366
|
+
expect(result.constructor.name).toBe('Uint8Array')
|
|
364
367
|
})
|
|
365
368
|
})
|
|
366
369
|
})
|
|
@@ -1,529 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit Tests - Role Configuration (app.config.ts)
|
|
3
|
-
*
|
|
4
|
-
* Tests role configuration for Developer Area feature:
|
|
5
|
-
* - Developer role in availableRoles array
|
|
6
|
-
* - Developer hierarchy value (100, highest)
|
|
7
|
-
* - Developer displayName translation key
|
|
8
|
-
* - Developer description
|
|
9
|
-
* - Configuration completeness
|
|
10
|
-
*
|
|
11
|
-
* Focus on developer role configuration validation.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { DEFAULT_APP_CONFIG } from '@nextsparkjs/core/lib/config/app.config'
|
|
15
|
-
|
|
16
|
-
describe('Role Configuration - Developer Role', () => {
|
|
17
|
-
describe('availableRoles Array', () => {
|
|
18
|
-
it('should include developer role', () => {
|
|
19
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
20
|
-
|
|
21
|
-
expect(roles).toContain('developer')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('should include superadmin role', () => {
|
|
25
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
26
|
-
|
|
27
|
-
expect(roles).toContain('superadmin')
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('should include member role', () => {
|
|
31
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
32
|
-
|
|
33
|
-
expect(roles).toContain('member')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('should have exactly 3 roles', () => {
|
|
37
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
38
|
-
|
|
39
|
-
expect(roles.length).toBe(3)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('should have roles in correct order', () => {
|
|
43
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
44
|
-
|
|
45
|
-
expect(roles).toEqual(['member', 'superadmin', 'developer'])
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('should have all roles as strings', () => {
|
|
49
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
50
|
-
|
|
51
|
-
roles.forEach(role => {
|
|
52
|
-
expect(typeof role).toBe('string')
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('should have all roles in lowercase', () => {
|
|
57
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
58
|
-
|
|
59
|
-
roles.forEach(role => {
|
|
60
|
-
expect(role).toBe(role.toLowerCase())
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('should not have duplicate roles', () => {
|
|
65
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
66
|
-
const uniqueRoles = new Set(roles)
|
|
67
|
-
|
|
68
|
-
expect(uniqueRoles.size).toBe(roles.length)
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
describe('Role Hierarchy', () => {
|
|
73
|
-
it('should have developer at level 100', () => {
|
|
74
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
75
|
-
|
|
76
|
-
expect(hierarchy.developer).toBe(100)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('should have superadmin at level 99', () => {
|
|
80
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
81
|
-
|
|
82
|
-
expect(hierarchy.superadmin).toBe(99)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('should have member at level 1', () => {
|
|
86
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
87
|
-
|
|
88
|
-
expect(hierarchy.member).toBe(1)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('should have developer as highest hierarchy', () => {
|
|
92
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
93
|
-
|
|
94
|
-
expect(hierarchy.developer).toBeGreaterThan(hierarchy.superadmin)
|
|
95
|
-
expect(hierarchy.developer).toBeGreaterThan(hierarchy.member)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('should have superadmin higher than member', () => {
|
|
99
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
100
|
-
|
|
101
|
-
expect(hierarchy.superadmin).toBeGreaterThan(hierarchy.member)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('should have all hierarchy values as numbers', () => {
|
|
105
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
106
|
-
|
|
107
|
-
expect(typeof hierarchy.developer).toBe('number')
|
|
108
|
-
expect(typeof hierarchy.superadmin).toBe('number')
|
|
109
|
-
expect(typeof hierarchy.member).toBe('number')
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('should have all hierarchy values as positive integers', () => {
|
|
113
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
114
|
-
|
|
115
|
-
expect(hierarchy.developer).toBeGreaterThan(0)
|
|
116
|
-
expect(hierarchy.superadmin).toBeGreaterThan(0)
|
|
117
|
-
expect(hierarchy.member).toBeGreaterThan(0)
|
|
118
|
-
|
|
119
|
-
expect(Number.isInteger(hierarchy.developer)).toBe(true)
|
|
120
|
-
expect(Number.isInteger(hierarchy.superadmin)).toBe(true)
|
|
121
|
-
expect(Number.isInteger(hierarchy.member)).toBe(true)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('should have correct hierarchy gap between developer and superadmin', () => {
|
|
125
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
126
|
-
|
|
127
|
-
// Developer (100) should be 1 level above superadmin (99)
|
|
128
|
-
expect(hierarchy.developer - hierarchy.superadmin).toBe(1)
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('should have large gap between superadmin and member', () => {
|
|
132
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
133
|
-
|
|
134
|
-
// Large gap indicates future extensibility
|
|
135
|
-
expect(hierarchy.superadmin - hierarchy.member).toBeGreaterThan(50)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('should have hierarchy keys matching availableRoles', () => {
|
|
139
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
140
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
141
|
-
|
|
142
|
-
roles.forEach(role => {
|
|
143
|
-
expect(hierarchy[role as keyof typeof hierarchy]).toBeDefined()
|
|
144
|
-
expect(typeof hierarchy[role as keyof typeof hierarchy]).toBe('number')
|
|
145
|
-
})
|
|
146
|
-
})
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
describe('Display Names (Translation Keys)', () => {
|
|
150
|
-
it('should have translation key for developer role', () => {
|
|
151
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
152
|
-
|
|
153
|
-
expect(displayNames.developer).toBe('common.userRoles.developer')
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('should have translation key for superadmin role', () => {
|
|
157
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
158
|
-
|
|
159
|
-
expect(displayNames.superadmin).toBe('common.userRoles.superadmin')
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('should have translation key for member role', () => {
|
|
163
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
164
|
-
|
|
165
|
-
expect(displayNames.member).toBe('common.userRoles.member')
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('should have translation keys in correct format', () => {
|
|
169
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
170
|
-
|
|
171
|
-
Object.values(displayNames).forEach(key => {
|
|
172
|
-
expect(key).toMatch(/^common\.userRoles\.\w+$/)
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('should have translation keys for all available roles', () => {
|
|
177
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
178
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
179
|
-
|
|
180
|
-
roles.forEach(role => {
|
|
181
|
-
expect(displayNames[role as keyof typeof displayNames]).toBeDefined()
|
|
182
|
-
expect(displayNames[role as keyof typeof displayNames]).toContain('common.userRoles.')
|
|
183
|
-
})
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('should use common namespace for all role names', () => {
|
|
187
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
188
|
-
|
|
189
|
-
Object.values(displayNames).forEach(key => {
|
|
190
|
-
expect(key).toMatch(/^common\./)
|
|
191
|
-
})
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
describe('Role Descriptions', () => {
|
|
196
|
-
it('should have description for developer role', () => {
|
|
197
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
198
|
-
|
|
199
|
-
expect(descriptions.developer).toBe('Ultimate access (platform developers only)')
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
it('should have description for superadmin role', () => {
|
|
203
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
204
|
-
|
|
205
|
-
expect(descriptions.superadmin).toBe('Full system access (product owners only)')
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
it('should have description for member role', () => {
|
|
209
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
210
|
-
|
|
211
|
-
expect(descriptions.member).toBe('Regular user with team-based permissions')
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('should have descriptions as non-empty strings', () => {
|
|
215
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
216
|
-
|
|
217
|
-
Object.values(descriptions).forEach(description => {
|
|
218
|
-
expect(typeof description).toBe('string')
|
|
219
|
-
expect(description.length).toBeGreaterThan(0)
|
|
220
|
-
})
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('should have descriptions for all available roles', () => {
|
|
224
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
225
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
226
|
-
|
|
227
|
-
roles.forEach(role => {
|
|
228
|
-
expect(descriptions[role as keyof typeof descriptions]).toBeDefined()
|
|
229
|
-
expect(descriptions[role as keyof typeof descriptions].length).toBeGreaterThan(0)
|
|
230
|
-
})
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
it('should have developer description indicating highest access', () => {
|
|
234
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
235
|
-
|
|
236
|
-
expect(descriptions.developer.toLowerCase()).toContain('ultimate')
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('should have developer description indicating platform developer usage', () => {
|
|
240
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
241
|
-
|
|
242
|
-
expect(descriptions.developer.toLowerCase()).toContain('developer')
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('should have superadmin description indicating product owner usage', () => {
|
|
246
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
247
|
-
|
|
248
|
-
expect(descriptions.superadmin.toLowerCase()).toContain('owner')
|
|
249
|
-
})
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
describe('Default Role', () => {
|
|
253
|
-
it('should have member as default role', () => {
|
|
254
|
-
const defaultRole = DEFAULT_APP_CONFIG.userRoles.defaultRole
|
|
255
|
-
|
|
256
|
-
expect(defaultRole).toBe('member')
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
it('should have default role in availableRoles', () => {
|
|
260
|
-
const defaultRole = DEFAULT_APP_CONFIG.userRoles.defaultRole
|
|
261
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
262
|
-
|
|
263
|
-
expect(roles).toContain(defaultRole)
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
it('should not have developer as default role', () => {
|
|
267
|
-
const defaultRole = DEFAULT_APP_CONFIG.userRoles.defaultRole
|
|
268
|
-
|
|
269
|
-
expect(defaultRole).not.toBe('developer')
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
it('should not have superadmin as default role', () => {
|
|
273
|
-
const defaultRole = DEFAULT_APP_CONFIG.userRoles.defaultRole
|
|
274
|
-
|
|
275
|
-
expect(defaultRole).not.toBe('superadmin')
|
|
276
|
-
})
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
describe('Configuration Completeness', () => {
|
|
280
|
-
it('should have all roles in hierarchy map', () => {
|
|
281
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
282
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
283
|
-
|
|
284
|
-
roles.forEach(role => {
|
|
285
|
-
expect(hierarchy[role as keyof typeof hierarchy]).toBeDefined()
|
|
286
|
-
})
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
it('should have all roles in displayNames map', () => {
|
|
290
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
291
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
292
|
-
|
|
293
|
-
roles.forEach(role => {
|
|
294
|
-
expect(displayNames[role as keyof typeof displayNames]).toBeDefined()
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
it('should have all roles in descriptions map', () => {
|
|
299
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
300
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
301
|
-
|
|
302
|
-
roles.forEach(role => {
|
|
303
|
-
expect(descriptions[role as keyof typeof descriptions]).toBeDefined()
|
|
304
|
-
})
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
it('should have no extra keys in hierarchy map', () => {
|
|
308
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
309
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
310
|
-
|
|
311
|
-
const hierarchyKeys = Object.keys(hierarchy)
|
|
312
|
-
|
|
313
|
-
expect(hierarchyKeys.length).toBe(roles.length)
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
it('should have no extra keys in displayNames map', () => {
|
|
317
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
318
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
319
|
-
|
|
320
|
-
const displayNameKeys = Object.keys(displayNames)
|
|
321
|
-
|
|
322
|
-
expect(displayNameKeys.length).toBe(roles.length)
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
it('should have no extra keys in descriptions map', () => {
|
|
326
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
327
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
328
|
-
|
|
329
|
-
const descriptionKeys = Object.keys(descriptions)
|
|
330
|
-
|
|
331
|
-
expect(descriptionKeys.length).toBe(roles.length)
|
|
332
|
-
})
|
|
333
|
-
})
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
describe('Role Configuration - Backwards Compatibility', () => {
|
|
337
|
-
describe('Existing Roles Preserved', () => {
|
|
338
|
-
it('should still have member role (Phase 1)', () => {
|
|
339
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
340
|
-
|
|
341
|
-
expect(roles).toContain('member')
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
it('should still have superadmin role (Phase 2)', () => {
|
|
345
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
346
|
-
|
|
347
|
-
expect(roles).toContain('superadmin')
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
it('should preserve member hierarchy value', () => {
|
|
351
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
352
|
-
|
|
353
|
-
expect(hierarchy.member).toBe(1)
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
it('should preserve superadmin hierarchy value', () => {
|
|
357
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
358
|
-
|
|
359
|
-
expect(hierarchy.superadmin).toBe(99)
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
it('should preserve default role as member', () => {
|
|
363
|
-
const defaultRole = DEFAULT_APP_CONFIG.userRoles.defaultRole
|
|
364
|
-
|
|
365
|
-
expect(defaultRole).toBe('member')
|
|
366
|
-
})
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
describe('New Role Addition (Phase 3)', () => {
|
|
370
|
-
it('should add developer role without breaking existing config', () => {
|
|
371
|
-
const config = DEFAULT_APP_CONFIG.userRoles
|
|
372
|
-
|
|
373
|
-
// All Phase 1-2 features still work
|
|
374
|
-
expect(config.defaultRole).toBe('member')
|
|
375
|
-
expect(config.availableRoles).toContain('member')
|
|
376
|
-
expect(config.availableRoles).toContain('superadmin')
|
|
377
|
-
|
|
378
|
-
// Phase 3 addition
|
|
379
|
-
expect(config.availableRoles).toContain('developer')
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
it('should have developer hierarchy higher than existing roles', () => {
|
|
383
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
384
|
-
|
|
385
|
-
expect(hierarchy.developer).toBeGreaterThan(hierarchy.superadmin)
|
|
386
|
-
expect(hierarchy.developer).toBeGreaterThan(hierarchy.member)
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
it('should not affect superadmin hierarchy relative to member', () => {
|
|
390
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
391
|
-
|
|
392
|
-
expect(hierarchy.superadmin).toBeGreaterThan(hierarchy.member)
|
|
393
|
-
})
|
|
394
|
-
})
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
describe('Role Configuration - Edge Cases', () => {
|
|
398
|
-
describe('Type Safety', () => {
|
|
399
|
-
it('should have userRoles as an object', () => {
|
|
400
|
-
expect(typeof DEFAULT_APP_CONFIG.userRoles).toBe('object')
|
|
401
|
-
expect(DEFAULT_APP_CONFIG.userRoles).not.toBeNull()
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
it('should have availableRoles as readonly array', () => {
|
|
405
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
406
|
-
|
|
407
|
-
expect(Array.isArray(roles)).toBe(true)
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
it('should have hierarchy as object with string keys', () => {
|
|
411
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
412
|
-
|
|
413
|
-
expect(typeof hierarchy).toBe('object')
|
|
414
|
-
Object.keys(hierarchy).forEach(key => {
|
|
415
|
-
expect(typeof key).toBe('string')
|
|
416
|
-
})
|
|
417
|
-
})
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
describe('Invalid Data Protection', () => {
|
|
421
|
-
it('should not have empty string roles', () => {
|
|
422
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
423
|
-
|
|
424
|
-
roles.forEach(role => {
|
|
425
|
-
expect(role.length).toBeGreaterThan(0)
|
|
426
|
-
})
|
|
427
|
-
})
|
|
428
|
-
|
|
429
|
-
it('should not have negative hierarchy values', () => {
|
|
430
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
431
|
-
|
|
432
|
-
Object.values(hierarchy).forEach(value => {
|
|
433
|
-
expect(value).toBeGreaterThan(0)
|
|
434
|
-
})
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
it('should not have hierarchy values as floats', () => {
|
|
438
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
439
|
-
|
|
440
|
-
Object.values(hierarchy).forEach(value => {
|
|
441
|
-
expect(Number.isInteger(value)).toBe(true)
|
|
442
|
-
})
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
it('should not have empty translation keys', () => {
|
|
446
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
447
|
-
|
|
448
|
-
Object.values(displayNames).forEach(key => {
|
|
449
|
-
expect(key.length).toBeGreaterThan(0)
|
|
450
|
-
})
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
it('should not have empty descriptions', () => {
|
|
454
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
455
|
-
|
|
456
|
-
Object.values(descriptions).forEach(description => {
|
|
457
|
-
expect(description.length).toBeGreaterThan(0)
|
|
458
|
-
})
|
|
459
|
-
})
|
|
460
|
-
})
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
describe('Real-World Configuration Usage', () => {
|
|
464
|
-
describe('Guard Implementation Support', () => {
|
|
465
|
-
it('should support DeveloperGuard role check', () => {
|
|
466
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
467
|
-
|
|
468
|
-
// DeveloperGuard checks if role === 'developer'
|
|
469
|
-
expect(roles).toContain('developer')
|
|
470
|
-
})
|
|
471
|
-
|
|
472
|
-
it('should support SuperAdminGuard dual role check', () => {
|
|
473
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
474
|
-
|
|
475
|
-
// SuperAdminGuard allows both superadmin and developer
|
|
476
|
-
expect(roles).toContain('superadmin')
|
|
477
|
-
expect(roles).toContain('developer')
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
it('should support middleware role validation', () => {
|
|
481
|
-
const hierarchy = DEFAULT_APP_CONFIG.userRoles.hierarchy
|
|
482
|
-
|
|
483
|
-
// Middleware compares hierarchy levels
|
|
484
|
-
expect(hierarchy.developer).toBeDefined()
|
|
485
|
-
expect(hierarchy.superadmin).toBeDefined()
|
|
486
|
-
expect(hierarchy.member).toBeDefined()
|
|
487
|
-
})
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
describe('UI Display Support', () => {
|
|
491
|
-
it('should provide translation keys for role selection dropdown', () => {
|
|
492
|
-
const displayNames = DEFAULT_APP_CONFIG.userRoles.displayNames
|
|
493
|
-
|
|
494
|
-
const roles = ['developer', 'superadmin', 'member'] as const
|
|
495
|
-
|
|
496
|
-
roles.forEach(role => {
|
|
497
|
-
expect(displayNames[role]).toMatch(/^common\.userRoles\.\w+$/)
|
|
498
|
-
})
|
|
499
|
-
})
|
|
500
|
-
|
|
501
|
-
it('should provide descriptions for role info tooltips', () => {
|
|
502
|
-
const descriptions = DEFAULT_APP_CONFIG.userRoles.descriptions
|
|
503
|
-
|
|
504
|
-
const roles = ['developer', 'superadmin', 'member'] as const
|
|
505
|
-
|
|
506
|
-
roles.forEach(role => {
|
|
507
|
-
expect(descriptions[role].length).toBeGreaterThan(10) // Meaningful description
|
|
508
|
-
})
|
|
509
|
-
})
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
describe('Database Schema Support', () => {
|
|
513
|
-
it('should have role values compatible with database enum', () => {
|
|
514
|
-
const roles = DEFAULT_APP_CONFIG.userRoles.availableRoles
|
|
515
|
-
|
|
516
|
-
// Database enum values should be lowercase strings
|
|
517
|
-
roles.forEach(role => {
|
|
518
|
-
expect(role).toMatch(/^[a-z]+$/)
|
|
519
|
-
})
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
it('should have default role for user creation', () => {
|
|
523
|
-
const defaultRole = DEFAULT_APP_CONFIG.userRoles.defaultRole
|
|
524
|
-
|
|
525
|
-
expect(defaultRole).toBe('member')
|
|
526
|
-
expect(DEFAULT_APP_CONFIG.userRoles.availableRoles).toContain(defaultRole)
|
|
527
|
-
})
|
|
528
|
-
})
|
|
529
|
-
})
|
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit Tests - User Role Helper Functions
|
|
3
|
-
*
|
|
4
|
-
* Tests role helper functions for Developer Area feature:
|
|
5
|
-
* - isDeveloper(): Check if user is developer
|
|
6
|
-
* - canAccessAdmin(): Check if user has Sector7 access
|
|
7
|
-
* - hasRoleLevel(): Hierarchy comparison with developer role
|
|
8
|
-
* - getAllRolesByHierarchy(): Verify developer is first (highest)
|
|
9
|
-
*
|
|
10
|
-
* Focus on developer role integration with existing role system.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { roleHelpers, USER_ROLES, ROLE_HIERARCHY, type UserRole } from '@nextsparkjs/core/types/user.types'
|
|
14
|
-
|
|
15
|
-
describe('User Role Helpers - Developer Role Integration', () => {
|
|
16
|
-
describe('isDeveloper()', () => {
|
|
17
|
-
it('should return true for developer role', () => {
|
|
18
|
-
const result = roleHelpers.isDeveloper(USER_ROLES.DEVELOPER)
|
|
19
|
-
expect(result).toBe(true)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('should return false for superadmin role', () => {
|
|
23
|
-
const result = roleHelpers.isDeveloper(USER_ROLES.SUPERADMIN)
|
|
24
|
-
expect(result).toBe(false)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('should return false for member role', () => {
|
|
28
|
-
const result = roleHelpers.isDeveloper(USER_ROLES.MEMBER)
|
|
29
|
-
expect(result).toBe(false)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('should handle string literal "developer"', () => {
|
|
33
|
-
const result = roleHelpers.isDeveloper('developer' as UserRole)
|
|
34
|
-
expect(result).toBe(true)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should handle case-sensitive comparison', () => {
|
|
38
|
-
// Should fail because role names are lowercase
|
|
39
|
-
const result = roleHelpers.isDeveloper('Developer' as UserRole)
|
|
40
|
-
expect(result).toBe(false)
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
describe('canAccessAdmin()', () => {
|
|
45
|
-
it('should return true for developer role', () => {
|
|
46
|
-
const result = roleHelpers.canAccessAdmin(USER_ROLES.DEVELOPER)
|
|
47
|
-
expect(result).toBe(true)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('should return true for superadmin role', () => {
|
|
51
|
-
const result = roleHelpers.canAccessAdmin(USER_ROLES.SUPERADMIN)
|
|
52
|
-
expect(result).toBe(true)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('should return false for member role', () => {
|
|
56
|
-
const result = roleHelpers.canAccessAdmin(USER_ROLES.MEMBER)
|
|
57
|
-
expect(result).toBe(false)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('should handle string literal "developer"', () => {
|
|
61
|
-
const result = roleHelpers.canAccessAdmin('developer' as UserRole)
|
|
62
|
-
expect(result).toBe(true)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('should handle string literal "superadmin"', () => {
|
|
66
|
-
const result = roleHelpers.canAccessAdmin('superadmin' as UserRole)
|
|
67
|
-
expect(result).toBe(true)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should handle both privileged roles', () => {
|
|
71
|
-
// Verify both roles have access (dual access pattern)
|
|
72
|
-
const developerAccess = roleHelpers.canAccessAdmin(USER_ROLES.DEVELOPER)
|
|
73
|
-
const superadminAccess = roleHelpers.canAccessAdmin(USER_ROLES.SUPERADMIN)
|
|
74
|
-
|
|
75
|
-
expect(developerAccess).toBe(true)
|
|
76
|
-
expect(superadminAccess).toBe(true)
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
describe('isSuperAdmin()', () => {
|
|
81
|
-
it('should return true for superadmin role', () => {
|
|
82
|
-
const result = roleHelpers.isSuperAdmin(USER_ROLES.SUPERADMIN)
|
|
83
|
-
expect(result).toBe(true)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('should return false for developer role', () => {
|
|
87
|
-
// Developer is NOT superadmin (separate role)
|
|
88
|
-
const result = roleHelpers.isSuperAdmin(USER_ROLES.DEVELOPER)
|
|
89
|
-
expect(result).toBe(false)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('should return false for member role', () => {
|
|
93
|
-
const result = roleHelpers.isSuperAdmin(USER_ROLES.MEMBER)
|
|
94
|
-
expect(result).toBe(false)
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
describe('hasRoleLevel() - Developer Hierarchy', () => {
|
|
99
|
-
it('should confirm developer has higher level than superadmin', () => {
|
|
100
|
-
const result = roleHelpers.hasRoleLevel(USER_ROLES.DEVELOPER, USER_ROLES.SUPERADMIN)
|
|
101
|
-
expect(result).toBe(true)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('should confirm developer has higher level than member', () => {
|
|
105
|
-
const result = roleHelpers.hasRoleLevel(USER_ROLES.DEVELOPER, USER_ROLES.MEMBER)
|
|
106
|
-
expect(result).toBe(true)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('should confirm superadmin does NOT have developer level', () => {
|
|
110
|
-
const result = roleHelpers.hasRoleLevel(USER_ROLES.SUPERADMIN, USER_ROLES.DEVELOPER)
|
|
111
|
-
expect(result).toBe(false)
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('should confirm member does NOT have developer level', () => {
|
|
115
|
-
const result = roleHelpers.hasRoleLevel(USER_ROLES.MEMBER, USER_ROLES.DEVELOPER)
|
|
116
|
-
expect(result).toBe(false)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('should confirm developer equals developer level', () => {
|
|
120
|
-
const result = roleHelpers.hasRoleLevel(USER_ROLES.DEVELOPER, USER_ROLES.DEVELOPER)
|
|
121
|
-
expect(result).toBe(true)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('should confirm superadmin has superadmin level', () => {
|
|
125
|
-
const result = roleHelpers.hasRoleLevel(USER_ROLES.SUPERADMIN, USER_ROLES.SUPERADMIN)
|
|
126
|
-
expect(result).toBe(true)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('should confirm superadmin has higher level than member', () => {
|
|
130
|
-
const result = roleHelpers.hasRoleLevel(USER_ROLES.SUPERADMIN, USER_ROLES.MEMBER)
|
|
131
|
-
expect(result).toBe(true)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('should confirm member does NOT have superadmin level', () => {
|
|
135
|
-
const result = roleHelpers.hasRoleLevel(USER_ROLES.MEMBER, USER_ROLES.SUPERADMIN)
|
|
136
|
-
expect(result).toBe(false)
|
|
137
|
-
})
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
describe('getAllRolesByHierarchy() - Developer First', () => {
|
|
141
|
-
it('should return developer as first role (highest hierarchy)', () => {
|
|
142
|
-
const roles = roleHelpers.getAllRolesByHierarchy()
|
|
143
|
-
|
|
144
|
-
expect(roles[0]).toBe(USER_ROLES.DEVELOPER)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should return superadmin as second role', () => {
|
|
148
|
-
const roles = roleHelpers.getAllRolesByHierarchy()
|
|
149
|
-
|
|
150
|
-
expect(roles[1]).toBe(USER_ROLES.SUPERADMIN)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('should return member as last role (lowest hierarchy)', () => {
|
|
154
|
-
const roles = roleHelpers.getAllRolesByHierarchy()
|
|
155
|
-
|
|
156
|
-
expect(roles[roles.length - 1]).toBe(USER_ROLES.MEMBER)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('should return all roles in descending hierarchy order', () => {
|
|
160
|
-
const roles = roleHelpers.getAllRolesByHierarchy()
|
|
161
|
-
|
|
162
|
-
expect(roles).toEqual([
|
|
163
|
-
USER_ROLES.DEVELOPER, // 100
|
|
164
|
-
USER_ROLES.SUPERADMIN, // 99
|
|
165
|
-
USER_ROLES.MEMBER // 1
|
|
166
|
-
])
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
it('should return array with correct length', () => {
|
|
170
|
-
const roles = roleHelpers.getAllRolesByHierarchy()
|
|
171
|
-
|
|
172
|
-
expect(roles.length).toBe(3)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('should not mutate original roles array', () => {
|
|
176
|
-
const roles1 = roleHelpers.getAllRolesByHierarchy()
|
|
177
|
-
const roles2 = roleHelpers.getAllRolesByHierarchy()
|
|
178
|
-
|
|
179
|
-
expect(roles1).toEqual(roles2)
|
|
180
|
-
expect(roles1).not.toBe(roles2) // Different array instances
|
|
181
|
-
})
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
describe('getRoleDisplayKey()', () => {
|
|
185
|
-
it('should return translation key for developer role', () => {
|
|
186
|
-
const key = roleHelpers.getRoleDisplayKey(USER_ROLES.DEVELOPER)
|
|
187
|
-
|
|
188
|
-
expect(key).toBe('common.userRoles.developer')
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('should return translation key for superadmin role', () => {
|
|
192
|
-
const key = roleHelpers.getRoleDisplayKey(USER_ROLES.SUPERADMIN)
|
|
193
|
-
|
|
194
|
-
expect(key).toBe('common.userRoles.superadmin')
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('should return translation key for member role', () => {
|
|
198
|
-
const key = roleHelpers.getRoleDisplayKey(USER_ROLES.MEMBER)
|
|
199
|
-
|
|
200
|
-
expect(key).toBe('common.userRoles.member')
|
|
201
|
-
})
|
|
202
|
-
})
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
describe('Role Hierarchy Configuration', () => {
|
|
206
|
-
describe('ROLE_HIERARCHY Values', () => {
|
|
207
|
-
it('should have developer at hierarchy level 100', () => {
|
|
208
|
-
expect(ROLE_HIERARCHY.developer).toBe(100)
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('should have superadmin at hierarchy level 99', () => {
|
|
212
|
-
expect(ROLE_HIERARCHY.superadmin).toBe(99)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('should have member at hierarchy level 1', () => {
|
|
216
|
-
expect(ROLE_HIERARCHY.member).toBe(1)
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
it('should have developer as highest hierarchy', () => {
|
|
220
|
-
const developerLevel = ROLE_HIERARCHY.developer
|
|
221
|
-
const superadminLevel = ROLE_HIERARCHY.superadmin
|
|
222
|
-
const memberLevel = ROLE_HIERARCHY.member
|
|
223
|
-
|
|
224
|
-
expect(developerLevel).toBeGreaterThan(superadminLevel)
|
|
225
|
-
expect(developerLevel).toBeGreaterThan(memberLevel)
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('should have superadmin higher than member', () => {
|
|
229
|
-
const superadminLevel = ROLE_HIERARCHY.superadmin
|
|
230
|
-
const memberLevel = ROLE_HIERARCHY.member
|
|
231
|
-
|
|
232
|
-
expect(superadminLevel).toBeGreaterThan(memberLevel)
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('should have numeric values for all roles', () => {
|
|
236
|
-
expect(typeof ROLE_HIERARCHY.developer).toBe('number')
|
|
237
|
-
expect(typeof ROLE_HIERARCHY.superadmin).toBe('number')
|
|
238
|
-
expect(typeof ROLE_HIERARCHY.member).toBe('number')
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
it('should have positive values for all roles', () => {
|
|
242
|
-
expect(ROLE_HIERARCHY.developer).toBeGreaterThan(0)
|
|
243
|
-
expect(ROLE_HIERARCHY.superadmin).toBeGreaterThan(0)
|
|
244
|
-
expect(ROLE_HIERARCHY.member).toBeGreaterThan(0)
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
it('should maintain expected hierarchy gap', () => {
|
|
248
|
-
// Developer (100) is 1 level above superadmin (99)
|
|
249
|
-
expect(ROLE_HIERARCHY.developer - ROLE_HIERARCHY.superadmin).toBe(1)
|
|
250
|
-
|
|
251
|
-
// Large gap between superadmin (99) and member (1)
|
|
252
|
-
expect(ROLE_HIERARCHY.superadmin - ROLE_HIERARCHY.member).toBe(98)
|
|
253
|
-
})
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
describe('USER_ROLES Constants', () => {
|
|
257
|
-
it('should have DEVELOPER constant', () => {
|
|
258
|
-
expect(USER_ROLES.DEVELOPER).toBe('developer')
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('should have SUPERADMIN constant', () => {
|
|
262
|
-
expect(USER_ROLES.SUPERADMIN).toBe('superadmin')
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
it('should have MEMBER constant', () => {
|
|
266
|
-
expect(USER_ROLES.MEMBER).toBe('member')
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
it('should have all constants as lowercase strings', () => {
|
|
270
|
-
expect(USER_ROLES.DEVELOPER).toBe(USER_ROLES.DEVELOPER.toLowerCase())
|
|
271
|
-
expect(USER_ROLES.SUPERADMIN).toBe(USER_ROLES.SUPERADMIN.toLowerCase())
|
|
272
|
-
expect(USER_ROLES.MEMBER).toBe(USER_ROLES.MEMBER.toLowerCase())
|
|
273
|
-
})
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
describe('Edge Cases and Error Handling', () => {
|
|
278
|
-
describe('hasRoleLevel() Edge Cases', () => {
|
|
279
|
-
it('should handle null-like values gracefully', () => {
|
|
280
|
-
// These would fail TypeScript compile-time but test runtime behavior
|
|
281
|
-
const result = roleHelpers.hasRoleLevel(
|
|
282
|
-
undefined as unknown as UserRole,
|
|
283
|
-
USER_ROLES.MEMBER
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
expect(result).toBe(false)
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
it('should handle comparing same role multiple times', () => {
|
|
290
|
-
const result1 = roleHelpers.hasRoleLevel(USER_ROLES.DEVELOPER, USER_ROLES.DEVELOPER)
|
|
291
|
-
const result2 = roleHelpers.hasRoleLevel(USER_ROLES.DEVELOPER, USER_ROLES.DEVELOPER)
|
|
292
|
-
|
|
293
|
-
expect(result1).toBe(result2)
|
|
294
|
-
expect(result1).toBe(true)
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
describe('isDeveloper() Edge Cases', () => {
|
|
299
|
-
it('should handle whitespace in role name', () => {
|
|
300
|
-
const result = roleHelpers.isDeveloper(' developer ' as UserRole)
|
|
301
|
-
expect(result).toBe(false) // Exact match required
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
it('should handle empty string', () => {
|
|
305
|
-
const result = roleHelpers.isDeveloper('' as UserRole)
|
|
306
|
-
expect(result).toBe(false)
|
|
307
|
-
})
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
describe('canAccessAdmin() Edge Cases', () => {
|
|
311
|
-
it('should handle invalid role gracefully', () => {
|
|
312
|
-
const result = roleHelpers.canAccessAdmin('invalid-role' as UserRole)
|
|
313
|
-
expect(result).toBe(false)
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
it('should handle null-like value', () => {
|
|
317
|
-
const result = roleHelpers.canAccessAdmin(null as unknown as UserRole)
|
|
318
|
-
expect(result).toBe(false)
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('should handle undefined', () => {
|
|
322
|
-
const result = roleHelpers.canAccessAdmin(undefined as unknown as UserRole)
|
|
323
|
-
expect(result).toBe(false)
|
|
324
|
-
})
|
|
325
|
-
})
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
describe('Real-World Use Cases', () => {
|
|
329
|
-
describe('Guard Logic Simulation', () => {
|
|
330
|
-
it('should allow developer through DeveloperGuard', () => {
|
|
331
|
-
const userRole: UserRole = 'developer'
|
|
332
|
-
const canAccess = roleHelpers.isDeveloper(userRole)
|
|
333
|
-
|
|
334
|
-
expect(canAccess).toBe(true)
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
it('should block superadmin from DeveloperGuard', () => {
|
|
338
|
-
const userRole: UserRole = 'superadmin'
|
|
339
|
-
const canAccess = roleHelpers.isDeveloper(userRole)
|
|
340
|
-
|
|
341
|
-
expect(canAccess).toBe(false)
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
it('should block member from DeveloperGuard', () => {
|
|
345
|
-
const userRole: UserRole = 'member'
|
|
346
|
-
const canAccess = roleHelpers.isDeveloper(userRole)
|
|
347
|
-
|
|
348
|
-
expect(canAccess).toBe(false)
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
it('should allow developer through SuperAdminGuard (Sector7)', () => {
|
|
352
|
-
const userRole: UserRole = 'developer'
|
|
353
|
-
const canAccess = roleHelpers.canAccessAdmin(userRole)
|
|
354
|
-
|
|
355
|
-
expect(canAccess).toBe(true)
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
it('should allow superadmin through SuperAdminGuard (Sector7)', () => {
|
|
359
|
-
const userRole: UserRole = 'superadmin'
|
|
360
|
-
const canAccess = roleHelpers.canAccessAdmin(userRole)
|
|
361
|
-
|
|
362
|
-
expect(canAccess).toBe(true)
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
it('should block member from SuperAdminGuard (Sector7)', () => {
|
|
366
|
-
const userRole: UserRole = 'member'
|
|
367
|
-
const canAccess = roleHelpers.canAccessAdmin(userRole)
|
|
368
|
-
|
|
369
|
-
expect(canAccess).toBe(false)
|
|
370
|
-
})
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
describe('Permission Checks', () => {
|
|
374
|
-
it('should grant developer access to all areas', () => {
|
|
375
|
-
const userRole: UserRole = 'developer'
|
|
376
|
-
|
|
377
|
-
const devArea = roleHelpers.isDeveloper(userRole)
|
|
378
|
-
const sector7 = roleHelpers.canAccessAdmin(userRole)
|
|
379
|
-
const hasAdminLevel = roleHelpers.hasRoleLevel(userRole, USER_ROLES.SUPERADMIN)
|
|
380
|
-
|
|
381
|
-
expect(devArea).toBe(true)
|
|
382
|
-
expect(sector7).toBe(true)
|
|
383
|
-
expect(hasAdminLevel).toBe(true)
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
it('should grant superadmin limited access (no /dev)', () => {
|
|
387
|
-
const userRole: UserRole = 'superadmin'
|
|
388
|
-
|
|
389
|
-
const devArea = roleHelpers.isDeveloper(userRole)
|
|
390
|
-
const sector7 = roleHelpers.canAccessAdmin(userRole)
|
|
391
|
-
const isSuperAdmin = roleHelpers.isSuperAdmin(userRole)
|
|
392
|
-
|
|
393
|
-
expect(devArea).toBe(false) // Cannot access /dev
|
|
394
|
-
expect(sector7).toBe(true) // Can access Sector7
|
|
395
|
-
expect(isSuperAdmin).toBe(true)
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
it('should grant member minimal access', () => {
|
|
399
|
-
const userRole: UserRole = 'member'
|
|
400
|
-
|
|
401
|
-
const devArea = roleHelpers.isDeveloper(userRole)
|
|
402
|
-
const sector7 = roleHelpers.canAccessAdmin(userRole)
|
|
403
|
-
const isSuperAdmin = roleHelpers.isSuperAdmin(userRole)
|
|
404
|
-
|
|
405
|
-
expect(devArea).toBe(false)
|
|
406
|
-
expect(sector7).toBe(false)
|
|
407
|
-
expect(isSuperAdmin).toBe(false)
|
|
408
|
-
})
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
describe('Role Display in UI', () => {
|
|
412
|
-
it('should provide translation keys for all roles', () => {
|
|
413
|
-
const roles = roleHelpers.getAllRolesByHierarchy()
|
|
414
|
-
|
|
415
|
-
roles.forEach(role => {
|
|
416
|
-
const key = roleHelpers.getRoleDisplayKey(role)
|
|
417
|
-
expect(key).toContain('common.userRoles.')
|
|
418
|
-
})
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
it('should list roles in correct order for admin UI', () => {
|
|
422
|
-
const roles = roleHelpers.getAllRolesByHierarchy()
|
|
423
|
-
|
|
424
|
-
// Admin UI should show roles from most powerful to least
|
|
425
|
-
expect(roles).toEqual([
|
|
426
|
-
'developer',
|
|
427
|
-
'superadmin',
|
|
428
|
-
'member'
|
|
429
|
-
])
|
|
430
|
-
})
|
|
431
|
-
})
|
|
432
|
-
})
|