@checkstack/auth-backend 0.0.2
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/CHANGELOG.md +142 -0
- package/drizzle/0000_minor_virginia_dare.sql +90 -0
- package/drizzle/0001_certain_madame_hydra.sql +20 -0
- package/drizzle/meta/0000_snapshot.json +580 -0
- package/drizzle/meta/0001_snapshot.json +717 -0
- package/drizzle/meta/_journal.json +20 -0
- package/drizzle.config.ts +7 -0
- package/package.json +34 -0
- package/src/hooks.ts +14 -0
- package/src/index.ts +878 -0
- package/src/meta-config.ts +13 -0
- package/src/platform-registration-config.ts +25 -0
- package/src/router.test.ts +440 -0
- package/src/router.ts +1051 -0
- package/src/schema.ts +173 -0
- package/src/utils/auth-error-redirect.ts +42 -0
- package/src/utils/user.test.ts +99 -0
- package/src/utils/user.ts +62 -0
- package/src/utils/validate-schema.test.ts +85 -0
- package/src/utils/validate-schema.ts +45 -0
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Meta-configuration schema for authentication strategies.
|
|
5
|
+
* Stores platform-level properties separate from strategy-specific config.
|
|
6
|
+
*/
|
|
7
|
+
export const strategyMetaConfigV1 = z.object({
|
|
8
|
+
enabled: z.boolean(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type StrategyMetaConfig = z.infer<typeof strategyMetaConfigV1>;
|
|
12
|
+
|
|
13
|
+
export const STRATEGY_META_CONFIG_VERSION = 1;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Platform-level configuration for user registration.
|
|
5
|
+
* This meta-config controls whether new users can register via any authentication method.
|
|
6
|
+
*/
|
|
7
|
+
export const platformRegistrationConfigV1 = z.object({
|
|
8
|
+
/**
|
|
9
|
+
* Whether new user registration is allowed.
|
|
10
|
+
* When false, only existing users can authenticate.
|
|
11
|
+
*/
|
|
12
|
+
allowRegistration: z
|
|
13
|
+
.boolean()
|
|
14
|
+
.default(true)
|
|
15
|
+
.describe(
|
|
16
|
+
"When enabled, new users can create accounts. When disabled, only existing users can sign in."
|
|
17
|
+
),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type PlatformRegistrationConfig = z.infer<
|
|
21
|
+
typeof platformRegistrationConfigV1
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
export const PLATFORM_REGISTRATION_CONFIG_VERSION = 1;
|
|
25
|
+
export const PLATFORM_REGISTRATION_CONFIG_ID = "platform.registration";
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { describe, it, expect, mock } from "bun:test";
|
|
2
|
+
import { createAuthRouter } from "./router";
|
|
3
|
+
import { createMockRpcContext } from "@checkstack/backend-api";
|
|
4
|
+
import { call } from "@orpc/server";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import * as schema from "./schema";
|
|
7
|
+
|
|
8
|
+
// Mock better-auth
|
|
9
|
+
const mockAuth: any = {
|
|
10
|
+
handler: mock(),
|
|
11
|
+
api: {
|
|
12
|
+
getSession: mock(),
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe("Auth Router", () => {
|
|
17
|
+
const mockUser = {
|
|
18
|
+
type: "user" as const,
|
|
19
|
+
id: "test-user",
|
|
20
|
+
permissions: ["*"],
|
|
21
|
+
roles: ["admin"],
|
|
22
|
+
} as any;
|
|
23
|
+
|
|
24
|
+
const createChain = (data: any = []) => {
|
|
25
|
+
const chain: any = {
|
|
26
|
+
where: mock(() => chain),
|
|
27
|
+
innerJoin: mock(() => chain),
|
|
28
|
+
limit: mock(() => chain),
|
|
29
|
+
offset: mock(() => chain),
|
|
30
|
+
orderBy: mock(() => chain),
|
|
31
|
+
onConflictDoUpdate: mock(() => Promise.resolve()),
|
|
32
|
+
then: (resolve: any) => Promise.resolve(resolve(data)),
|
|
33
|
+
};
|
|
34
|
+
return chain;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const mockDb: any = {
|
|
38
|
+
select: mock(() => ({
|
|
39
|
+
from: mock(() => createChain([])),
|
|
40
|
+
})),
|
|
41
|
+
insert: mock(() => ({
|
|
42
|
+
values: mock(() => createChain()),
|
|
43
|
+
})),
|
|
44
|
+
delete: mock(() => ({
|
|
45
|
+
where: mock(() => Promise.resolve()),
|
|
46
|
+
})),
|
|
47
|
+
transaction: mock((cb: any) => cb(mockDb)), // Updated reference to mockDb
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const mockRegistry = {
|
|
51
|
+
getStrategies: () => [
|
|
52
|
+
{
|
|
53
|
+
id: "credential",
|
|
54
|
+
displayName: "Credentials",
|
|
55
|
+
description: "Email and password authentication",
|
|
56
|
+
configSchema: z.object({ enabled: z.boolean() }),
|
|
57
|
+
configVersion: 1,
|
|
58
|
+
migrations: [],
|
|
59
|
+
requiresManualRegistration: true,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const mockConfigService: any = {
|
|
65
|
+
get: mock(() => Promise.resolve(undefined)),
|
|
66
|
+
getRedacted: mock(() => Promise.resolve({})),
|
|
67
|
+
set: mock(() => Promise.resolve()),
|
|
68
|
+
delete: mock(() => Promise.resolve()),
|
|
69
|
+
list: mock(() => Promise.resolve([])),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const mockPermissionRegistry = {
|
|
73
|
+
getPermissions: () => [
|
|
74
|
+
{ id: "auth-backend.users.read", description: "List all users" },
|
|
75
|
+
{ id: "auth-backend.users.manage", description: "Delete users" },
|
|
76
|
+
{ id: "auth-backend.roles.read", description: "Read and list roles" },
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const router = createAuthRouter(
|
|
81
|
+
mockDb,
|
|
82
|
+
mockRegistry,
|
|
83
|
+
async () => {},
|
|
84
|
+
mockConfigService,
|
|
85
|
+
mockPermissionRegistry
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
it("getPermissions returns current user permissions", async () => {
|
|
89
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
90
|
+
const result = await call(router.permissions, undefined, { context });
|
|
91
|
+
expect(result.permissions).toContain("*");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("getUsers lists users with roles", async () => {
|
|
95
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
96
|
+
|
|
97
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
98
|
+
from: mock(() =>
|
|
99
|
+
createChain([{ id: "1", email: "user1@test.com", name: "User 1" }])
|
|
100
|
+
),
|
|
101
|
+
}));
|
|
102
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
103
|
+
from: mock(() => createChain([{ userId: "1", roleId: "admin" }])),
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
const result = await call(router.getUsers, undefined, { context });
|
|
107
|
+
expect(result).toHaveLength(1);
|
|
108
|
+
expect(result[0].roles).toContain("admin");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("deleteUser prevents deleting initial admin", async () => {
|
|
112
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
113
|
+
expect(
|
|
114
|
+
call(router.deleteUser, "initial-admin-id", { context })
|
|
115
|
+
).rejects.toThrow("Cannot delete initial admin");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("deleteUser cascades to delete related records", async () => {
|
|
119
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
120
|
+
const userId = "user-to-delete";
|
|
121
|
+
|
|
122
|
+
// Track which tables had delete called on them
|
|
123
|
+
const deletedTables: any[] = [];
|
|
124
|
+
const mockTx: any = {
|
|
125
|
+
delete: mock((table: any) => {
|
|
126
|
+
deletedTables.push(table); // Track table
|
|
127
|
+
return {
|
|
128
|
+
where: mock(() => Promise.resolve()),
|
|
129
|
+
};
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
mockDb.transaction.mockImplementationOnce((cb: any) => cb(mockTx));
|
|
134
|
+
|
|
135
|
+
await call(router.deleteUser, userId, { context });
|
|
136
|
+
|
|
137
|
+
// Verify transaction was used
|
|
138
|
+
expect(mockDb.transaction).toHaveBeenCalled();
|
|
139
|
+
|
|
140
|
+
// Verify all related tables were deleted in order
|
|
141
|
+
expect(deletedTables).toHaveLength(4);
|
|
142
|
+
expect(deletedTables.includes(schema.userRole)).toBe(true);
|
|
143
|
+
expect(deletedTables.includes(schema.session)).toBe(true);
|
|
144
|
+
expect(deletedTables.includes(schema.account)).toBe(true);
|
|
145
|
+
expect(deletedTables.includes(schema.user)).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("getRoles returns all roles with permissions", async () => {
|
|
149
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
150
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
151
|
+
from: mock(() => createChain([{ id: "admin", name: "Admin" }])),
|
|
152
|
+
}));
|
|
153
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
154
|
+
from: mock(() =>
|
|
155
|
+
createChain([{ roleId: "admin", permissionId: "users.manage" }])
|
|
156
|
+
),
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
const result = await call(router.getRoles, undefined, { context });
|
|
160
|
+
expect(result).toHaveLength(1);
|
|
161
|
+
expect(result[0].id).toBe("admin");
|
|
162
|
+
expect(result[0].permissions).toContain("users.manage");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("updateUserRoles updates user roles", async () => {
|
|
166
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
167
|
+
|
|
168
|
+
const result = await call(
|
|
169
|
+
router.updateUserRoles,
|
|
170
|
+
{ userId: "other-user", roles: ["admin"] },
|
|
171
|
+
{ context }
|
|
172
|
+
);
|
|
173
|
+
// updateUserRoles returns void, so just check it completed
|
|
174
|
+
expect(result).toBeUndefined();
|
|
175
|
+
expect(mockDb.transaction).toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("updateUserRoles prevents updating own roles", async () => {
|
|
179
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
180
|
+
|
|
181
|
+
expect(
|
|
182
|
+
call(
|
|
183
|
+
router.updateUserRoles,
|
|
184
|
+
{ userId: "test-user", roles: ["admin"] },
|
|
185
|
+
{ context }
|
|
186
|
+
)
|
|
187
|
+
).rejects.toThrow("Cannot update your own roles");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("getStrategies returns available strategies", async () => {
|
|
191
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
192
|
+
|
|
193
|
+
const result = await call(router.getStrategies, undefined, { context });
|
|
194
|
+
expect(result.some((s: any) => s.id === "credential")).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("updateStrategy updates strategy enabled status", async () => {
|
|
198
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
199
|
+
|
|
200
|
+
const result = await call(
|
|
201
|
+
router.updateStrategy,
|
|
202
|
+
{ id: "credential", enabled: false },
|
|
203
|
+
{ context }
|
|
204
|
+
);
|
|
205
|
+
expect(result.success).toBe(true);
|
|
206
|
+
expect(mockConfigService.set).toHaveBeenCalled();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("getRegistrationStatus returns default true", async () => {
|
|
210
|
+
const context = createMockRpcContext({ user: undefined }); // Public endpoint
|
|
211
|
+
const result = await call(router.getRegistrationStatus, undefined, {
|
|
212
|
+
context,
|
|
213
|
+
});
|
|
214
|
+
expect(result.allowRegistration).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("setRegistrationStatus updates flag and requires permission", async () => {
|
|
218
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
219
|
+
const result = await call(
|
|
220
|
+
router.setRegistrationStatus,
|
|
221
|
+
{ allowRegistration: false },
|
|
222
|
+
{ context }
|
|
223
|
+
);
|
|
224
|
+
expect(result.success).toBe(true);
|
|
225
|
+
expect(mockConfigService.set).toHaveBeenCalledWith(
|
|
226
|
+
"platform.registration",
|
|
227
|
+
expect.anything(),
|
|
228
|
+
1,
|
|
229
|
+
{ allowRegistration: false }
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ==========================================================================
|
|
234
|
+
// SERVICE-TO-SERVICE TESTS
|
|
235
|
+
// ==========================================================================
|
|
236
|
+
|
|
237
|
+
const mockServiceUser = {
|
|
238
|
+
type: "service" as const,
|
|
239
|
+
pluginId: "auth-ldap-backend",
|
|
240
|
+
} as any;
|
|
241
|
+
|
|
242
|
+
it("findUserByEmail returns user when found", async () => {
|
|
243
|
+
const context = createMockRpcContext({ user: mockServiceUser });
|
|
244
|
+
|
|
245
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
246
|
+
from: mock(() => createChain([{ id: "user-123" }])),
|
|
247
|
+
}));
|
|
248
|
+
|
|
249
|
+
const result = await call(
|
|
250
|
+
router.findUserByEmail,
|
|
251
|
+
{ email: "test@example.com" },
|
|
252
|
+
{ context }
|
|
253
|
+
);
|
|
254
|
+
expect(result).toEqual({ id: "user-123" });
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("findUserByEmail returns undefined when not found", async () => {
|
|
258
|
+
const context = createMockRpcContext({ user: mockServiceUser });
|
|
259
|
+
|
|
260
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
261
|
+
from: mock(() => createChain([])),
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
const result = await call(
|
|
265
|
+
router.findUserByEmail,
|
|
266
|
+
{ email: "nonexistent@example.com" },
|
|
267
|
+
{ context }
|
|
268
|
+
);
|
|
269
|
+
expect(result).toBeUndefined();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("upsertExternalUser creates new user and account", async () => {
|
|
273
|
+
const context = createMockRpcContext({ user: mockServiceUser });
|
|
274
|
+
|
|
275
|
+
// Mock user not found (empty result)
|
|
276
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
277
|
+
from: mock(() => createChain([])),
|
|
278
|
+
}));
|
|
279
|
+
|
|
280
|
+
// Mock registration allowed
|
|
281
|
+
mockConfigService.get.mockResolvedValueOnce({ allowRegistration: true });
|
|
282
|
+
|
|
283
|
+
const result = await call(
|
|
284
|
+
router.upsertExternalUser,
|
|
285
|
+
{
|
|
286
|
+
email: "ldap-user@example.com",
|
|
287
|
+
name: "LDAP User",
|
|
288
|
+
providerId: "ldap",
|
|
289
|
+
accountId: "ldapuser",
|
|
290
|
+
password: "hashed-password",
|
|
291
|
+
},
|
|
292
|
+
{ context }
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
expect(result.created).toBe(true);
|
|
296
|
+
expect(result.userId).toBeDefined();
|
|
297
|
+
expect(mockDb.transaction).toHaveBeenCalled();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("upsertExternalUser updates existing user when autoUpdateUser is true", async () => {
|
|
301
|
+
const context = createMockRpcContext({ user: mockServiceUser });
|
|
302
|
+
|
|
303
|
+
// Mock existing user found
|
|
304
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
305
|
+
from: mock(() => createChain([{ id: "existing-user-id" }])),
|
|
306
|
+
}));
|
|
307
|
+
|
|
308
|
+
// Mock update chain
|
|
309
|
+
mockDb.update = mock(() => ({
|
|
310
|
+
set: mock(() => ({
|
|
311
|
+
where: mock(() => Promise.resolve()),
|
|
312
|
+
})),
|
|
313
|
+
}));
|
|
314
|
+
|
|
315
|
+
const result = await call(
|
|
316
|
+
router.upsertExternalUser,
|
|
317
|
+
{
|
|
318
|
+
email: "existing@example.com",
|
|
319
|
+
name: "Updated Name",
|
|
320
|
+
providerId: "ldap",
|
|
321
|
+
accountId: "existinguser",
|
|
322
|
+
password: "hashed-password",
|
|
323
|
+
autoUpdateUser: true,
|
|
324
|
+
},
|
|
325
|
+
{ context }
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(result.created).toBe(false);
|
|
329
|
+
expect(result.userId).toBe("existing-user-id");
|
|
330
|
+
expect(mockDb.update).toHaveBeenCalled();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("createSession creates session record", async () => {
|
|
334
|
+
const context = createMockRpcContext({ user: mockServiceUser });
|
|
335
|
+
|
|
336
|
+
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
337
|
+
|
|
338
|
+
const result = await call(
|
|
339
|
+
router.createSession,
|
|
340
|
+
{
|
|
341
|
+
userId: "user-123",
|
|
342
|
+
token: "session-token",
|
|
343
|
+
expiresAt,
|
|
344
|
+
},
|
|
345
|
+
{ context }
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
expect(result.sessionId).toBeDefined();
|
|
349
|
+
expect(mockDb.insert).toHaveBeenCalled();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// ==========================================================================
|
|
353
|
+
// ADMIN USER CREATION TESTS
|
|
354
|
+
// ==========================================================================
|
|
355
|
+
|
|
356
|
+
it("createCredentialUser creates user with valid data", async () => {
|
|
357
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
358
|
+
|
|
359
|
+
// Mock credential strategy enabled
|
|
360
|
+
mockConfigService.get.mockResolvedValueOnce({ enabled: true });
|
|
361
|
+
|
|
362
|
+
// Mock user not found (empty result for email check)
|
|
363
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
364
|
+
from: mock(() => createChain([])),
|
|
365
|
+
}));
|
|
366
|
+
|
|
367
|
+
const result = await call(
|
|
368
|
+
router.createCredentialUser,
|
|
369
|
+
{
|
|
370
|
+
email: "newuser@example.com",
|
|
371
|
+
name: "New User",
|
|
372
|
+
password: "ValidPass123",
|
|
373
|
+
},
|
|
374
|
+
{ context }
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
expect(result.userId).toBeDefined();
|
|
378
|
+
expect(mockDb.transaction).toHaveBeenCalled();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("createCredentialUser rejects weak password", async () => {
|
|
382
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
383
|
+
|
|
384
|
+
// Weak password - no uppercase
|
|
385
|
+
expect(
|
|
386
|
+
call(
|
|
387
|
+
router.createCredentialUser,
|
|
388
|
+
{
|
|
389
|
+
email: "test@example.com",
|
|
390
|
+
name: "Test User",
|
|
391
|
+
password: "weakpass1",
|
|
392
|
+
},
|
|
393
|
+
{ context }
|
|
394
|
+
)
|
|
395
|
+
).rejects.toThrow("uppercase");
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("createCredentialUser rejects duplicate email", async () => {
|
|
399
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
400
|
+
|
|
401
|
+
// Mock credential strategy enabled
|
|
402
|
+
mockConfigService.get.mockResolvedValueOnce({ enabled: true });
|
|
403
|
+
|
|
404
|
+
// Mock user already exists
|
|
405
|
+
mockDb.select.mockImplementationOnce(() => ({
|
|
406
|
+
from: mock(() => createChain([{ id: "existing-user" }])),
|
|
407
|
+
}));
|
|
408
|
+
|
|
409
|
+
expect(
|
|
410
|
+
call(
|
|
411
|
+
router.createCredentialUser,
|
|
412
|
+
{
|
|
413
|
+
email: "existing@example.com",
|
|
414
|
+
name: "Existing User",
|
|
415
|
+
password: "ValidPass123",
|
|
416
|
+
},
|
|
417
|
+
{ context }
|
|
418
|
+
)
|
|
419
|
+
).rejects.toThrow("already exists");
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("createCredentialUser rejects when credential strategy disabled", async () => {
|
|
423
|
+
const context = createMockRpcContext({ user: mockUser });
|
|
424
|
+
|
|
425
|
+
// Mock credential strategy disabled
|
|
426
|
+
mockConfigService.get.mockResolvedValueOnce({ enabled: false });
|
|
427
|
+
|
|
428
|
+
expect(
|
|
429
|
+
call(
|
|
430
|
+
router.createCredentialUser,
|
|
431
|
+
{
|
|
432
|
+
email: "test@example.com",
|
|
433
|
+
name: "Test User",
|
|
434
|
+
password: "ValidPass123",
|
|
435
|
+
},
|
|
436
|
+
{ context }
|
|
437
|
+
)
|
|
438
|
+
).rejects.toThrow("not enabled");
|
|
439
|
+
});
|
|
440
|
+
});
|