create-mercato-app 0.4.6-develop-0c668dfcae → 0.4.6-develop-e10452e5ca
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
CHANGED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests the backend catch-all route guarding for requireFeatures.
|
|
3
|
-
*/
|
|
4
|
-
import React from 'react'
|
|
5
|
-
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
6
|
-
// Avoid loading the full generated modules (which pull example modules and DSL)
|
|
7
|
-
jest.mock('@/generated/modules.generated', () => ({ modules: [] }))
|
|
8
|
-
|
|
9
|
-
import BackendCatchAll from '@/app/(backend)/backend/[...slug]/page'
|
|
10
|
-
|
|
11
|
-
// Mock UI breadcrumb component to avoid UI package dependency
|
|
12
|
-
jest.mock('@open-mercato/ui/backend/AppShell', () => ({
|
|
13
|
-
ApplyBreadcrumb: () => React.createElement('div', null, 'Breadcrumb'),
|
|
14
|
-
}))
|
|
15
|
-
|
|
16
|
-
// Mock UI CrudForm to avoid importing ESM-only deps like remark-gfm in Jest
|
|
17
|
-
jest.mock('@open-mercato/ui/backend/CrudForm', () => ({
|
|
18
|
-
CrudForm: () => React.createElement('form', null, React.createElement('div', null, 'CrudFormMock')),
|
|
19
|
-
}))
|
|
20
|
-
|
|
21
|
-
const cookieStore = { get: jest.fn() }
|
|
22
|
-
const cookiesMock = jest.fn(() => cookieStore)
|
|
23
|
-
jest.mock('next/headers', () => ({
|
|
24
|
-
cookies: () => cookiesMock(),
|
|
25
|
-
}))
|
|
26
|
-
|
|
27
|
-
// Mock registry to return a match with requireFeatures
|
|
28
|
-
jest.mock('@open-mercato/shared/modules/registry', () => ({
|
|
29
|
-
findBackendMatch: jest.fn(() => ({
|
|
30
|
-
route: {
|
|
31
|
-
requireAuth: true,
|
|
32
|
-
requireRoles: [],
|
|
33
|
-
requireFeatures: ['entities.records.view'],
|
|
34
|
-
title: 'Test',
|
|
35
|
-
Component: () => React.createElement('div', null, 'OK'),
|
|
36
|
-
},
|
|
37
|
-
params: {},
|
|
38
|
-
})),
|
|
39
|
-
}))
|
|
40
|
-
|
|
41
|
-
// Mock auth cookie reader
|
|
42
|
-
jest.mock('@open-mercato/shared/lib/auth/server', () => ({
|
|
43
|
-
getAuthFromCookies: jest.fn(),
|
|
44
|
-
}))
|
|
45
|
-
|
|
46
|
-
// Mock DI container
|
|
47
|
-
const mockRbac = {
|
|
48
|
-
userHasAllFeatures: jest.fn<
|
|
49
|
-
ReturnType<RbacService['userHasAllFeatures']>,
|
|
50
|
-
Parameters<RbacService['userHasAllFeatures']>
|
|
51
|
-
>()
|
|
52
|
-
}
|
|
53
|
-
jest.mock('@open-mercato/shared/lib/di/container', () => ({
|
|
54
|
-
createRequestContainer: async () => ({
|
|
55
|
-
resolve: (key: string) => (key === 'rbacService' ? mockRbac : null),
|
|
56
|
-
}),
|
|
57
|
-
}))
|
|
58
|
-
|
|
59
|
-
// Mock next/navigation redirect and notFound
|
|
60
|
-
const redirect = jest.fn((href?: string) => { throw new Error('REDIRECT ' + href) })
|
|
61
|
-
const notFound = jest.fn(() => { throw new Error('NOT_FOUND') })
|
|
62
|
-
jest.mock('next/navigation', () => ({
|
|
63
|
-
redirect: (href?: string) => redirect(href),
|
|
64
|
-
notFound: () => notFound(),
|
|
65
|
-
}))
|
|
66
|
-
|
|
67
|
-
type GetAuthFromCookies = typeof import('@open-mercato/shared/lib/auth/server')['getAuthFromCookies']
|
|
68
|
-
|
|
69
|
-
async function setAuthMock(value: Awaited<ReturnType<GetAuthFromCookies>>) {
|
|
70
|
-
const authModule = await import('@open-mercato/shared/lib/auth/server')
|
|
71
|
-
const mocked = authModule.getAuthFromCookies as jest.MockedFunction<GetAuthFromCookies>
|
|
72
|
-
mocked.mockResolvedValue(value)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
describe('Backend requireFeatures guard', () => {
|
|
76
|
-
beforeEach(() => {
|
|
77
|
-
jest.clearAllMocks()
|
|
78
|
-
mockRbac.userHasAllFeatures.mockResolvedValue(true)
|
|
79
|
-
cookieStore.get.mockReset()
|
|
80
|
-
cookieStore.get.mockReturnValue(undefined)
|
|
81
|
-
cookiesMock.mockClear()
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('renders component when features are satisfied', async () => {
|
|
85
|
-
await setAuthMock({ sub: 'u1', tenantId: 't1', orgId: 'o1', roles: [] })
|
|
86
|
-
|
|
87
|
-
const el = await BackendCatchAll({ params: Promise.resolve({ slug: ['entities', 'records'] }) })
|
|
88
|
-
expect(el).toBeTruthy()
|
|
89
|
-
expect(redirect).not.toHaveBeenCalled()
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('redirects to refresh if not authenticated', async () => {
|
|
93
|
-
await setAuthMock(null)
|
|
94
|
-
|
|
95
|
-
await expect(
|
|
96
|
-
BackendCatchAll({ params: Promise.resolve({ slug: ['entities', 'records'] }) })
|
|
97
|
-
).rejects.toThrow(/REDIRECT \/api\/auth\/session\/refresh/)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('redirects to login when RBAC denies required features', async () => {
|
|
101
|
-
await setAuthMock({ sub: 'u1', tenantId: 't1', orgId: 'o1', roles: [] })
|
|
102
|
-
mockRbac.userHasAllFeatures.mockResolvedValueOnce(false)
|
|
103
|
-
|
|
104
|
-
await expect(
|
|
105
|
-
BackendCatchAll({ params: Promise.resolve({ slug: ['entities', 'records'] }) })
|
|
106
|
-
).rejects.toThrow(/REDIRECT \/login\?requireFeature=/)
|
|
107
|
-
})
|
|
108
|
-
})
|