canopycms-next 0.0.0 → 0.0.1
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 +4 -5
- package/src/adapter.test.ts +0 -194
- package/src/adapter.ts +0 -105
- package/src/client.tsx +0 -36
- package/src/context-wrapper.ts +0 -87
- package/src/index.ts +0 -5
- package/src/test-utils.ts +0 -42
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopycms-next",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "Next.js adapter for CanopyCMS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"./client": "./src/client.tsx"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
21
|
-
"src"
|
|
20
|
+
"dist"
|
|
22
21
|
],
|
|
23
22
|
"publishConfig": {
|
|
24
23
|
"exports": {
|
|
@@ -41,8 +40,8 @@
|
|
|
41
40
|
"node": ">=18"
|
|
42
41
|
},
|
|
43
42
|
"peerDependencies": {
|
|
44
|
-
"canopycms": "
|
|
45
|
-
"next": "^
|
|
43
|
+
"canopycms": "^0.0.1",
|
|
44
|
+
"next": "^0.0.1",
|
|
46
45
|
"react": "^18.0.0 || ^19.0.0"
|
|
47
46
|
},
|
|
48
47
|
"devDependencies": {
|
package/src/adapter.test.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
// Mock next/server before any imports
|
|
4
|
-
vi.mock('next/server', () => {
|
|
5
|
-
return {
|
|
6
|
-
NextResponse: {
|
|
7
|
-
json: (body: any, init?: any) => ({
|
|
8
|
-
body,
|
|
9
|
-
status: init?.status ?? 200,
|
|
10
|
-
headers: init?.headers,
|
|
11
|
-
}),
|
|
12
|
-
},
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
// Mock canopycms/http to return a controlled response
|
|
17
|
-
vi.mock('canopycms/http', async () => {
|
|
18
|
-
return {
|
|
19
|
-
createCanopyRequestHandler: vi.fn(() => {
|
|
20
|
-
return async (req: any, segments: string[]) => {
|
|
21
|
-
// Return different responses based on segments
|
|
22
|
-
if (segments.length === 1 && segments[0] === 'branches') {
|
|
23
|
-
return {
|
|
24
|
-
status: 200,
|
|
25
|
-
body: { ok: true, status: 200, data: { branches: [] } },
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
if (segments.length === 0 || segments.includes('unknown')) {
|
|
29
|
-
return {
|
|
30
|
-
status: 404,
|
|
31
|
-
body: { ok: false, status: 404, error: 'Not found' },
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
status: 200,
|
|
36
|
-
body: { ok: true, status: 200 },
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}),
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
import { createCanopyCatchAllHandler } from './adapter'
|
|
44
|
-
import { createMockAuthPlugin } from './test-utils'
|
|
45
|
-
|
|
46
|
-
describe('Next.js adapter', () => {
|
|
47
|
-
const mockAuthPlugin = createMockAuthPlugin({
|
|
48
|
-
userId: 'test-user',
|
|
49
|
-
groups: ['Admins'],
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
describe('createCanopyCatchAllHandler', () => {
|
|
53
|
-
it('converts NextRequest to CanopyRequest and returns NextResponse', async () => {
|
|
54
|
-
const handler = createCanopyCatchAllHandler({
|
|
55
|
-
services: {} as any,
|
|
56
|
-
authPlugin: mockAuthPlugin,
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const mockNextRequest = {
|
|
60
|
-
method: 'GET',
|
|
61
|
-
url: 'http://localhost:3000/api/canopycms/branches',
|
|
62
|
-
headers: { get: () => null },
|
|
63
|
-
json: async () => undefined,
|
|
64
|
-
} as any
|
|
65
|
-
|
|
66
|
-
const response: any = await handler(mockNextRequest, {
|
|
67
|
-
params: { canopycms: ['branches'] },
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
expect(response.status).toBe(200)
|
|
71
|
-
expect(response.body).toHaveProperty('ok', true)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('handles Next.js 14 direct params object', async () => {
|
|
75
|
-
const handler = createCanopyCatchAllHandler({
|
|
76
|
-
services: {} as any,
|
|
77
|
-
authPlugin: mockAuthPlugin,
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const mockNextRequest = {
|
|
81
|
-
method: 'GET',
|
|
82
|
-
url: 'http://localhost:3000/api/canopycms/branches',
|
|
83
|
-
headers: { get: () => null },
|
|
84
|
-
json: async () => undefined,
|
|
85
|
-
} as any
|
|
86
|
-
|
|
87
|
-
// Next.js 14 style - params is a direct object
|
|
88
|
-
const response: any = await handler(mockNextRequest, {
|
|
89
|
-
params: { canopycms: ['branches'] },
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
expect(response.status).toBe(200)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('handles Next.js 15 async params Promise', async () => {
|
|
96
|
-
const handler = createCanopyCatchAllHandler({
|
|
97
|
-
services: {} as any,
|
|
98
|
-
authPlugin: mockAuthPlugin,
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const mockNextRequest = {
|
|
102
|
-
method: 'GET',
|
|
103
|
-
url: 'http://localhost:3000/api/canopycms/branches',
|
|
104
|
-
headers: { get: () => null },
|
|
105
|
-
json: async () => undefined,
|
|
106
|
-
} as any
|
|
107
|
-
|
|
108
|
-
// Next.js 15 style - params is a Promise
|
|
109
|
-
const response: any = await handler(mockNextRequest, {
|
|
110
|
-
params: Promise.resolve({ canopycms: ['branches'] }),
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
expect(response.status).toBe(200)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('handles missing params gracefully', async () => {
|
|
117
|
-
const handler = createCanopyCatchAllHandler({
|
|
118
|
-
services: {} as any,
|
|
119
|
-
authPlugin: mockAuthPlugin,
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
const mockNextRequest = {
|
|
123
|
-
method: 'GET',
|
|
124
|
-
url: 'http://localhost:3000/api/canopycms',
|
|
125
|
-
headers: { get: () => null },
|
|
126
|
-
json: async () => undefined,
|
|
127
|
-
} as any
|
|
128
|
-
|
|
129
|
-
// No params at all
|
|
130
|
-
const response: any = await handler(mockNextRequest, undefined)
|
|
131
|
-
|
|
132
|
-
expect(response.status).toBe(404)
|
|
133
|
-
})
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
describe('wrapNextRequest', () => {
|
|
138
|
-
it('wraps NextRequest correctly', async () => {
|
|
139
|
-
const { wrapNextRequest } = await import('./adapter')
|
|
140
|
-
|
|
141
|
-
const mockReq = {
|
|
142
|
-
method: 'POST',
|
|
143
|
-
url: 'http://localhost:3000/api/canopycms/branches',
|
|
144
|
-
headers: {
|
|
145
|
-
get: (name: string) =>
|
|
146
|
-
name.toLowerCase() === 'authorization' ? 'Bearer test-token' : null,
|
|
147
|
-
},
|
|
148
|
-
json: async () => ({ name: 'test-branch' }),
|
|
149
|
-
} as any
|
|
150
|
-
|
|
151
|
-
const wrapped = wrapNextRequest(mockReq)
|
|
152
|
-
|
|
153
|
-
expect(wrapped.method).toBe('POST')
|
|
154
|
-
expect(wrapped.url).toBe('http://localhost:3000/api/canopycms/branches')
|
|
155
|
-
expect(wrapped.header('Authorization')).toBe('Bearer test-token')
|
|
156
|
-
expect(await wrapped.json()).toEqual({ name: 'test-branch' })
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('returns null for missing headers', async () => {
|
|
160
|
-
const { wrapNextRequest } = await import('./adapter')
|
|
161
|
-
|
|
162
|
-
const mockReq = {
|
|
163
|
-
method: 'GET',
|
|
164
|
-
url: 'http://localhost:3000/api/test',
|
|
165
|
-
headers: {
|
|
166
|
-
get: () => null,
|
|
167
|
-
},
|
|
168
|
-
json: async () => undefined,
|
|
169
|
-
} as any
|
|
170
|
-
|
|
171
|
-
const wrapped = wrapNextRequest(mockReq)
|
|
172
|
-
|
|
173
|
-
expect(wrapped.header('X-Custom-Header')).toBeNull()
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('returns undefined for GET request body', async () => {
|
|
177
|
-
const { wrapNextRequest } = await import('./adapter')
|
|
178
|
-
|
|
179
|
-
const mockReq = {
|
|
180
|
-
method: 'GET',
|
|
181
|
-
url: 'http://localhost:3000/api/test',
|
|
182
|
-
headers: {
|
|
183
|
-
get: () => null,
|
|
184
|
-
},
|
|
185
|
-
json: async () => {
|
|
186
|
-
throw new Error('No body')
|
|
187
|
-
},
|
|
188
|
-
} as any
|
|
189
|
-
|
|
190
|
-
const wrapped = wrapNextRequest(mockReq)
|
|
191
|
-
|
|
192
|
-
expect(await wrapped.json()).toBeUndefined()
|
|
193
|
-
})
|
|
194
|
-
})
|
package/src/adapter.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import type { NextRequest } from 'next/server'
|
|
2
|
-
import { NextResponse } from 'next/server'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
createCanopyRequestHandler,
|
|
6
|
-
type CanopyHandlerOptions,
|
|
7
|
-
type CanopyRequest,
|
|
8
|
-
type CanopyResponse,
|
|
9
|
-
} from 'canopycms/http'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Options for creating a Canopy Next.js handler.
|
|
13
|
-
* Same as core CanopyHandlerOptions - re-exported for convenience.
|
|
14
|
-
*/
|
|
15
|
-
export interface CanopyNextOptions extends CanopyHandlerOptions {}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Wrap a Next.js request to implement the CanopyRequest interface.
|
|
19
|
-
*/
|
|
20
|
-
export function wrapNextRequest(req: NextRequest): CanopyRequest {
|
|
21
|
-
return {
|
|
22
|
-
method: req.method,
|
|
23
|
-
url: req.url,
|
|
24
|
-
|
|
25
|
-
header(name: string): string | null {
|
|
26
|
-
return req.headers.get(name)
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
async json(): Promise<unknown> {
|
|
30
|
-
if (req.method === 'GET') return undefined
|
|
31
|
-
try {
|
|
32
|
-
return await req.json()
|
|
33
|
-
} catch {
|
|
34
|
-
return undefined
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Convert a CanopyResponse to a NextResponse.
|
|
42
|
-
*/
|
|
43
|
-
function toNextResponse(response: CanopyResponse<unknown>): ReturnType<typeof NextResponse.json> {
|
|
44
|
-
return NextResponse.json(response.body, {
|
|
45
|
-
status: response.status,
|
|
46
|
-
headers: response.headers,
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Extract path segments from Next.js catch-all route params.
|
|
52
|
-
* Handles both Next.js 14 (direct object) and Next.js 15 (Promise) params.
|
|
53
|
-
*/
|
|
54
|
-
async function extractPathSegments(ctx?: {
|
|
55
|
-
params?: Promise<{ canopycms?: string[] }> | { canopycms?: string[] }
|
|
56
|
-
}): Promise<string[]> {
|
|
57
|
-
if (!ctx?.params) return []
|
|
58
|
-
const resolvedParams = ctx.params instanceof Promise ? await ctx.params : ctx.params
|
|
59
|
-
return (resolvedParams?.canopycms ?? []).filter(Boolean)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Catch-all Next.js handler for a single API route (e.g., /api/canopycms/[...canopycms]).
|
|
64
|
-
*
|
|
65
|
-
* This is a thin adapter that:
|
|
66
|
-
* 1. Converts NextRequest to CanopyRequest
|
|
67
|
-
* 2. Extracts path segments from Next.js params
|
|
68
|
-
* 3. Delegates to the core handler
|
|
69
|
-
* 4. Converts CanopyResponse to NextResponse
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* ```ts
|
|
73
|
-
* // app/api/canopycms/[...canopycms]/route.ts
|
|
74
|
-
* import { createCanopyCatchAllHandler } from 'canopycms-next'
|
|
75
|
-
* import { createClerkAuthPlugin } from 'canopycms-auth-clerk'
|
|
76
|
-
* import config from '../../../../canopycms.config'
|
|
77
|
-
*
|
|
78
|
-
* const handler = createCanopyCatchAllHandler({
|
|
79
|
-
* config: config.server,
|
|
80
|
-
* authPlugin: createClerkAuthPlugin({ useOrganizationsAsGroups: true }),
|
|
81
|
-
* })
|
|
82
|
-
*
|
|
83
|
-
* export const GET = handler
|
|
84
|
-
* export const POST = handler
|
|
85
|
-
* export const PUT = handler
|
|
86
|
-
* export const DELETE = handler
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
export const createCanopyCatchAllHandler = (options: CanopyNextOptions) => {
|
|
90
|
-
const coreHandler = createCanopyRequestHandler(options)
|
|
91
|
-
|
|
92
|
-
return async (
|
|
93
|
-
req: NextRequest,
|
|
94
|
-
ctx?: {
|
|
95
|
-
params?:
|
|
96
|
-
| Promise<{ canopycms?: string[]; [key: string]: any }>
|
|
97
|
-
| { canopycms?: string[]; [key: string]: any }
|
|
98
|
-
},
|
|
99
|
-
) => {
|
|
100
|
-
const canopyReq = wrapNextRequest(req)
|
|
101
|
-
const segments = await extractPathSegments(ctx)
|
|
102
|
-
const response = await coreHandler(canopyReq, segments)
|
|
103
|
-
return toNextResponse(response)
|
|
104
|
-
}
|
|
105
|
-
}
|
package/src/client.tsx
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useSearchParams } from 'next/navigation'
|
|
4
|
-
import { CanopyEditorPage } from 'canopycms/client'
|
|
5
|
-
import type { CanopyClientConfig } from 'canopycms/client'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Next.js-specific wrapper for CanopyEditorPage that automatically reads
|
|
9
|
-
* URL search params (branch, entry) using Next.js's useSearchParams hook.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```tsx
|
|
13
|
-
* // app/edit/page.tsx
|
|
14
|
-
* 'use client'
|
|
15
|
-
* import { NextCanopyEditorPage } from 'canopycms-next/client'
|
|
16
|
-
* import config from '../../canopycms.config'
|
|
17
|
-
*
|
|
18
|
-
* export default function EditPage() {
|
|
19
|
-
* const clientConfig = config.client()
|
|
20
|
-
* const EditorPage = NextCanopyEditorPage(clientConfig)
|
|
21
|
-
* return <EditorPage />
|
|
22
|
-
* }
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
export const NextCanopyEditorPage = (config: CanopyClientConfig) => {
|
|
26
|
-
const CorePage = CanopyEditorPage(config)
|
|
27
|
-
|
|
28
|
-
return function NextEditorPage() {
|
|
29
|
-
const urlSearchParams = useSearchParams()
|
|
30
|
-
const searchParams = {
|
|
31
|
-
branch: urlSearchParams.get('branch') ?? undefined,
|
|
32
|
-
entry: urlSearchParams.get('entry') ?? undefined,
|
|
33
|
-
}
|
|
34
|
-
return <CorePage searchParams={searchParams} />
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/context-wrapper.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { cache } from 'react'
|
|
2
|
-
import { headers } from 'next/headers'
|
|
3
|
-
import { createCanopyContext, type CanopyContext, createCanopyServices } from 'canopycms/server'
|
|
4
|
-
import type { CanopyConfig, AuthPlugin, CanopyUser, FieldConfig } from 'canopycms'
|
|
5
|
-
import { authResultToCanopyUser } from 'canopycms'
|
|
6
|
-
import { loadInternalGroups, loadBranchContext } from 'canopycms/server'
|
|
7
|
-
import type { InternalGroup } from 'canopycms/server'
|
|
8
|
-
import { createCanopyCatchAllHandler } from './adapter'
|
|
9
|
-
|
|
10
|
-
let warnedNoAdmins = false
|
|
11
|
-
|
|
12
|
-
export interface NextCanopyOptions {
|
|
13
|
-
config: CanopyConfig
|
|
14
|
-
authPlugin: AuthPlugin
|
|
15
|
-
entrySchemaRegistry: Record<string, readonly FieldConfig[]>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Create Next.js-specific wrapper around core context.
|
|
20
|
-
* Adds React cache() for per-request memoization and API handler.
|
|
21
|
-
* This function is async because it needs to load .collection.json meta files.
|
|
22
|
-
*/
|
|
23
|
-
export async function createNextCanopyContext(options: NextCanopyOptions) {
|
|
24
|
-
// Create services ONCE at initialization
|
|
25
|
-
const services = await createCanopyServices(options.config, {
|
|
26
|
-
entrySchemaRegistry: options.entrySchemaRegistry,
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
// User extractor: passes Next.js headers to auth plugin, loads internal groups, applies authorization
|
|
30
|
-
const extractUser = async (): Promise<CanopyUser> => {
|
|
31
|
-
const headersList = await headers()
|
|
32
|
-
const authResult = await options.authPlugin.authenticate(headersList)
|
|
33
|
-
|
|
34
|
-
// Load internal groups from main branch
|
|
35
|
-
const baseBranch = services.config.defaultBaseBranch ?? 'main'
|
|
36
|
-
const operatingMode = services.config.mode ?? 'dev'
|
|
37
|
-
const mainBranchContext = await loadBranchContext({
|
|
38
|
-
branchName: baseBranch,
|
|
39
|
-
mode: operatingMode,
|
|
40
|
-
})
|
|
41
|
-
const internalGroups: InternalGroup[] = mainBranchContext
|
|
42
|
-
? await loadInternalGroups(
|
|
43
|
-
mainBranchContext.branchRoot,
|
|
44
|
-
operatingMode,
|
|
45
|
-
services.bootstrapAdminIds,
|
|
46
|
-
).catch((err: unknown) => {
|
|
47
|
-
console.warn('CanopyCMS: Failed to load internal groups from main branch:', err)
|
|
48
|
-
return [] as InternalGroup[]
|
|
49
|
-
})
|
|
50
|
-
: []
|
|
51
|
-
|
|
52
|
-
if (!warnedNoAdmins && Array.isArray(internalGroups)) {
|
|
53
|
-
const adminsGroup = internalGroups.find((g) => g.id === 'Admins')
|
|
54
|
-
if (!adminsGroup || adminsGroup.members.length === 0) {
|
|
55
|
-
console.warn(
|
|
56
|
-
'CanopyCMS: No admin users configured. Set CANOPY_BOOTSTRAP_ADMIN_IDS or add members to the Admins group.',
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
warnedNoAdmins = true
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return authResultToCanopyUser(authResult, services.bootstrapAdminIds, internalGroups)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Create core context with pre-created services (framework-agnostic)
|
|
66
|
-
const coreContext = createCanopyContext({
|
|
67
|
-
services,
|
|
68
|
-
extractUser,
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
// Wrap with React cache() for per-request caching
|
|
72
|
-
const getCanopy = cache((): Promise<CanopyContext> => {
|
|
73
|
-
return coreContext.getContext()
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
// Create API handler using same services
|
|
77
|
-
const handler = createCanopyCatchAllHandler({
|
|
78
|
-
...options,
|
|
79
|
-
services,
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
getCanopy,
|
|
84
|
-
handler,
|
|
85
|
-
services,
|
|
86
|
-
}
|
|
87
|
-
}
|
package/src/index.ts
DELETED
package/src/test-utils.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { AuthPlugin } from 'canopycms/auth'
|
|
2
|
-
import type { AuthenticatedUser } from 'canopycms'
|
|
3
|
-
|
|
4
|
-
const ADMINS = 'Admins'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Create a mock AuthPlugin for testing.
|
|
8
|
-
* Returns a valid user by default (as Admin), or can be configured to return specific users.
|
|
9
|
-
*/
|
|
10
|
-
export const createMockAuthPlugin = (
|
|
11
|
-
user: AuthenticatedUser = {
|
|
12
|
-
type: 'authenticated',
|
|
13
|
-
userId: 'test-user',
|
|
14
|
-
groups: [ADMINS],
|
|
15
|
-
},
|
|
16
|
-
): AuthPlugin => ({
|
|
17
|
-
authenticate: async () => ({
|
|
18
|
-
success: true,
|
|
19
|
-
user: {
|
|
20
|
-
userId: user.userId,
|
|
21
|
-
email: user.email,
|
|
22
|
-
name: user.name,
|
|
23
|
-
avatarUrl: user.avatarUrl,
|
|
24
|
-
externalGroups: user.groups,
|
|
25
|
-
},
|
|
26
|
-
}),
|
|
27
|
-
searchUsers: async () => [],
|
|
28
|
-
getUserMetadata: async () => null,
|
|
29
|
-
getGroupMetadata: async () => null,
|
|
30
|
-
listGroups: async () => [],
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create a mock AuthPlugin that rejects all authentication.
|
|
35
|
-
*/
|
|
36
|
-
export const createRejectingAuthPlugin = (error = 'Unauthorized'): AuthPlugin => ({
|
|
37
|
-
authenticate: async () => ({ success: false, error }),
|
|
38
|
-
searchUsers: async () => [],
|
|
39
|
-
getUserMetadata: async () => null,
|
|
40
|
-
getGroupMetadata: async () => null,
|
|
41
|
-
listGroups: async () => [],
|
|
42
|
-
})
|