@better-auth/test-utils 1.5.0-beta.9
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/.turbo/turbo-build.log +17 -0
- package/LICENSE.md +20 -0
- package/dist/adapter.d.mts +3 -0
- package/dist/adapter.mjs +4 -0
- package/dist/create-test-suite.d.mts +118 -0
- package/dist/create-test-suite.mjs +461 -0
- package/dist/test-adapter.d.mts +75 -0
- package/dist/test-adapter.mjs +162 -0
- package/package.json +31 -0
- package/src/adapter/create-test-suite.ts +898 -0
- package/src/adapter/index.ts +7 -0
- package/src/adapter/test-adapter.ts +384 -0
- package/tsconfig.json +11 -0
- package/tsdown.config.ts +12 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import type { Awaitable, BetterAuthOptions } from "@better-auth/core";
|
|
2
|
+
import type { DBAdapter } from "@better-auth/core/db/adapter";
|
|
3
|
+
import { deepmerge, initGetModelName } from "@better-auth/core/db/adapter";
|
|
4
|
+
import { TTY_COLORS } from "@better-auth/core/env";
|
|
5
|
+
import { getAuthTables } from "better-auth/db";
|
|
6
|
+
import { afterAll, beforeAll, describe } from "vitest";
|
|
7
|
+
import type { createTestSuite, TestSuiteStats } from "./create-test-suite";
|
|
8
|
+
|
|
9
|
+
export type Logger = {
|
|
10
|
+
info: (...args: any[]) => void;
|
|
11
|
+
success: (...args: any[]) => void;
|
|
12
|
+
warn: (...args: any[]) => void;
|
|
13
|
+
error: (...args: any[]) => void;
|
|
14
|
+
debug: (...args: any[]) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const testAdapter = async ({
|
|
18
|
+
adapter: getAdapter,
|
|
19
|
+
runMigrations,
|
|
20
|
+
overrideBetterAuthOptions,
|
|
21
|
+
additionalCleanups,
|
|
22
|
+
tests,
|
|
23
|
+
prefixTests,
|
|
24
|
+
onFinish,
|
|
25
|
+
customIdGenerator,
|
|
26
|
+
transformIdOutput,
|
|
27
|
+
}: {
|
|
28
|
+
/**
|
|
29
|
+
* A function that will return the adapter instance to test with.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* testAdapter({
|
|
34
|
+
* adapter: (options) => drizzleAdapter(drizzle(db), {
|
|
35
|
+
* schema: generateSchema(options),
|
|
36
|
+
* }),
|
|
37
|
+
* })
|
|
38
|
+
*/
|
|
39
|
+
adapter: (
|
|
40
|
+
options: BetterAuthOptions,
|
|
41
|
+
) => Awaitable<(options: BetterAuthOptions) => DBAdapter<BetterAuthOptions>>;
|
|
42
|
+
/**
|
|
43
|
+
* A function that will run the database migrations.
|
|
44
|
+
*/
|
|
45
|
+
runMigrations: (betterAuthOptions: BetterAuthOptions) => Promise<void> | void;
|
|
46
|
+
/**
|
|
47
|
+
* Any potential better-auth options overrides.
|
|
48
|
+
*/
|
|
49
|
+
overrideBetterAuthOptions?: (
|
|
50
|
+
betterAuthOptions: BetterAuthOptions,
|
|
51
|
+
) => BetterAuthOptions;
|
|
52
|
+
/**
|
|
53
|
+
* By default we will cleanup all tables automatically,
|
|
54
|
+
* but if you have additional cleanup logic, you can pass it here.
|
|
55
|
+
*
|
|
56
|
+
* Such as deleting a DB file that could had been created.
|
|
57
|
+
*/
|
|
58
|
+
additionalCleanups?: () => Promise<void> | void;
|
|
59
|
+
/**
|
|
60
|
+
* A test suite to run.
|
|
61
|
+
*/
|
|
62
|
+
tests: ReturnType<ReturnType<typeof createTestSuite>>[];
|
|
63
|
+
/**
|
|
64
|
+
* A prefix to add to the test suite name.
|
|
65
|
+
*/
|
|
66
|
+
prefixTests?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Upon finish of the tests, this function will be called.
|
|
69
|
+
*/
|
|
70
|
+
onFinish?: () => Promise<void> | void;
|
|
71
|
+
/**
|
|
72
|
+
* Custom ID generator function to be used by the helper functions. (such as `insertRandom`)
|
|
73
|
+
*/
|
|
74
|
+
customIdGenerator?: () => any;
|
|
75
|
+
/**
|
|
76
|
+
* A function that will transform the ID output.
|
|
77
|
+
*/
|
|
78
|
+
transformIdOutput?: (id: any) => any;
|
|
79
|
+
}) => {
|
|
80
|
+
const defaultBAOptions = {} satisfies BetterAuthOptions;
|
|
81
|
+
let betterAuthOptions = (() => {
|
|
82
|
+
return {
|
|
83
|
+
...defaultBAOptions,
|
|
84
|
+
...(overrideBetterAuthOptions?.(defaultBAOptions) || {}),
|
|
85
|
+
} satisfies BetterAuthOptions;
|
|
86
|
+
})();
|
|
87
|
+
|
|
88
|
+
let adapter: DBAdapter<BetterAuthOptions> = (
|
|
89
|
+
await getAdapter(betterAuthOptions)
|
|
90
|
+
)(betterAuthOptions);
|
|
91
|
+
|
|
92
|
+
const adapterName = adapter.options?.adapterConfig.adapterName;
|
|
93
|
+
const adapterId = adapter.options?.adapterConfig.adapterId || adapter.id;
|
|
94
|
+
const adapterDisplayName = adapterName || adapterId;
|
|
95
|
+
|
|
96
|
+
const refreshAdapter = async (betterAuthOptions: BetterAuthOptions) => {
|
|
97
|
+
adapter = (await getAdapter(betterAuthOptions))(betterAuthOptions);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A helper function to log to the console.
|
|
102
|
+
*/
|
|
103
|
+
const log: Logger = (() => {
|
|
104
|
+
return {
|
|
105
|
+
info: (...args: any[]) =>
|
|
106
|
+
console.log(
|
|
107
|
+
`${TTY_COLORS.fg.blue}INFO ${TTY_COLORS.reset} [${adapterDisplayName}]`,
|
|
108
|
+
...args,
|
|
109
|
+
),
|
|
110
|
+
success: (...args: any[]) =>
|
|
111
|
+
console.log(
|
|
112
|
+
`${TTY_COLORS.fg.green}SUCCESS${TTY_COLORS.reset} [${adapterDisplayName}]`,
|
|
113
|
+
...args,
|
|
114
|
+
),
|
|
115
|
+
warn: (...args: any[]) =>
|
|
116
|
+
console.log(
|
|
117
|
+
`${TTY_COLORS.fg.yellow}WARN ${TTY_COLORS.reset} [${adapterDisplayName}]`,
|
|
118
|
+
...args,
|
|
119
|
+
),
|
|
120
|
+
error: (...args: any[]) =>
|
|
121
|
+
console.log(
|
|
122
|
+
`${TTY_COLORS.fg.red}ERROR ${TTY_COLORS.reset} [${adapterDisplayName}]`,
|
|
123
|
+
...args,
|
|
124
|
+
),
|
|
125
|
+
debug: (...args: any[]) =>
|
|
126
|
+
console.log(
|
|
127
|
+
`${TTY_COLORS.fg.magenta}DEBUG ${TTY_COLORS.reset} [${adapterDisplayName}]`,
|
|
128
|
+
...args,
|
|
129
|
+
),
|
|
130
|
+
};
|
|
131
|
+
})();
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Cleanup function to remove all rows from the database.
|
|
135
|
+
*/
|
|
136
|
+
const cleanup = async () => {
|
|
137
|
+
const start = performance.now();
|
|
138
|
+
await refreshAdapter(betterAuthOptions);
|
|
139
|
+
const getAllModels = getAuthTables(betterAuthOptions);
|
|
140
|
+
|
|
141
|
+
// Clean up all rows from all models
|
|
142
|
+
for (const model of Object.keys(getAllModels)) {
|
|
143
|
+
const getModelName = initGetModelName({
|
|
144
|
+
usePlural: adapter.options?.adapterConfig?.usePlural,
|
|
145
|
+
schema: getAllModels,
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
const modelName = getModelName(model);
|
|
149
|
+
await adapter.deleteMany({ model: modelName, where: [] });
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const msg = `Error while cleaning up all rows from ${model}`;
|
|
152
|
+
log.error(msg, error);
|
|
153
|
+
throw new Error(msg, {
|
|
154
|
+
cause: error,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Run additional cleanups
|
|
160
|
+
try {
|
|
161
|
+
await additionalCleanups?.();
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const msg = `Error while running additional cleanups`;
|
|
164
|
+
log.error(msg, error);
|
|
165
|
+
throw new Error(msg, {
|
|
166
|
+
cause: error,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
await refreshAdapter(betterAuthOptions);
|
|
170
|
+
log.success(
|
|
171
|
+
`${TTY_COLORS.bright}CLEAN-UP${TTY_COLORS.reset} completed successfully (${(performance.now() - start).toFixed(3)}ms)`,
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* A function that will run the database migrations.
|
|
177
|
+
*/
|
|
178
|
+
const migrate = async () => {
|
|
179
|
+
const start = performance.now();
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await runMigrations(betterAuthOptions);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
const msg = `Error while running migrations`;
|
|
185
|
+
log.error(msg, error);
|
|
186
|
+
throw new Error(msg, {
|
|
187
|
+
cause: error,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
log.success(
|
|
191
|
+
`${TTY_COLORS.bright}MIGRATIONS${TTY_COLORS.reset} completed successfully (${(performance.now() - start).toFixed(3)}ms)`,
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
execute: () => {
|
|
197
|
+
describe(adapterDisplayName, async () => {
|
|
198
|
+
// Collect statistics from all test suites
|
|
199
|
+
const allSuiteStats: TestSuiteStats[] = [];
|
|
200
|
+
|
|
201
|
+
beforeAll(async () => {
|
|
202
|
+
await migrate();
|
|
203
|
+
}, 60000);
|
|
204
|
+
|
|
205
|
+
afterAll(async () => {
|
|
206
|
+
await cleanup();
|
|
207
|
+
|
|
208
|
+
// Display statistics summary
|
|
209
|
+
if (allSuiteStats.length > 0) {
|
|
210
|
+
const totalMigrations = allSuiteStats.reduce(
|
|
211
|
+
(sum, stats) => sum + stats.migrationCount,
|
|
212
|
+
0,
|
|
213
|
+
);
|
|
214
|
+
const totalMigrationTime = allSuiteStats.reduce(
|
|
215
|
+
(sum, stats) => sum + stats.totalMigrationTime,
|
|
216
|
+
0,
|
|
217
|
+
);
|
|
218
|
+
const totalTests = allSuiteStats.reduce(
|
|
219
|
+
(sum, stats) => sum + stats.testCount,
|
|
220
|
+
0,
|
|
221
|
+
);
|
|
222
|
+
const totalDuration = allSuiteStats.reduce(
|
|
223
|
+
(sum, stats) => sum + stats.suiteDuration,
|
|
224
|
+
0,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const dash = "─";
|
|
228
|
+
const separator = `${dash.repeat(80)}`;
|
|
229
|
+
|
|
230
|
+
console.log(`\n${TTY_COLORS.fg.cyan}${separator}`);
|
|
231
|
+
console.log(
|
|
232
|
+
`${TTY_COLORS.fg.cyan}${TTY_COLORS.bright}TEST SUITE STATISTICS SUMMARY${TTY_COLORS.reset}`,
|
|
233
|
+
);
|
|
234
|
+
console.log(
|
|
235
|
+
`${TTY_COLORS.fg.cyan}${separator}${TTY_COLORS.reset}\n`,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Per-suite breakdown
|
|
239
|
+
for (const stats of allSuiteStats) {
|
|
240
|
+
const avgMigrationTime =
|
|
241
|
+
stats.migrationCount > 0
|
|
242
|
+
? (stats.totalMigrationTime / stats.migrationCount).toFixed(2)
|
|
243
|
+
: "0.00";
|
|
244
|
+
console.log(
|
|
245
|
+
`${TTY_COLORS.fg.magenta}${stats.suiteName}${TTY_COLORS.reset}:`,
|
|
246
|
+
);
|
|
247
|
+
console.log(
|
|
248
|
+
` Tests: ${TTY_COLORS.fg.green}${stats.testCount}${TTY_COLORS.reset}`,
|
|
249
|
+
);
|
|
250
|
+
console.log(
|
|
251
|
+
` Migrations: ${TTY_COLORS.fg.yellow}${stats.migrationCount}${TTY_COLORS.reset} (avg: ${avgMigrationTime}ms)`,
|
|
252
|
+
);
|
|
253
|
+
console.log(
|
|
254
|
+
` Total Migration Time: ${TTY_COLORS.fg.yellow}${stats.totalMigrationTime.toFixed(2)}ms${TTY_COLORS.reset}`,
|
|
255
|
+
);
|
|
256
|
+
console.log(
|
|
257
|
+
` Suite Duration: ${TTY_COLORS.fg.blue}${stats.suiteDuration.toFixed(2)}ms${TTY_COLORS.reset}`,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Display grouping statistics if available
|
|
261
|
+
if (stats.groupingStats) {
|
|
262
|
+
const {
|
|
263
|
+
totalGroups,
|
|
264
|
+
averageTestsPerGroup,
|
|
265
|
+
largestGroupSize,
|
|
266
|
+
smallestGroupSize,
|
|
267
|
+
groupsWithMultipleTests,
|
|
268
|
+
} = stats.groupingStats;
|
|
269
|
+
console.log(
|
|
270
|
+
` Test Groups: ${TTY_COLORS.fg.cyan}${totalGroups}${TTY_COLORS.reset}`,
|
|
271
|
+
);
|
|
272
|
+
if (totalGroups > 0) {
|
|
273
|
+
console.log(
|
|
274
|
+
` Avg Tests/Group: ${TTY_COLORS.fg.cyan}${averageTestsPerGroup.toFixed(2)}${TTY_COLORS.reset}`,
|
|
275
|
+
);
|
|
276
|
+
console.log(
|
|
277
|
+
` Largest Group: ${TTY_COLORS.fg.cyan}${largestGroupSize}${TTY_COLORS.reset}`,
|
|
278
|
+
);
|
|
279
|
+
console.log(
|
|
280
|
+
` Smallest Group: ${TTY_COLORS.fg.cyan}${smallestGroupSize}${TTY_COLORS.reset}`,
|
|
281
|
+
);
|
|
282
|
+
console.log(
|
|
283
|
+
` Groups w/ Multiple Tests: ${TTY_COLORS.fg.cyan}${groupsWithMultipleTests}${TTY_COLORS.reset}`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log("");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Totals
|
|
292
|
+
const avgMigrationTime =
|
|
293
|
+
totalMigrations > 0
|
|
294
|
+
? (totalMigrationTime / totalMigrations).toFixed(2)
|
|
295
|
+
: "0.00";
|
|
296
|
+
|
|
297
|
+
// Calculate total grouping statistics
|
|
298
|
+
const totalGroups = allSuiteStats.reduce(
|
|
299
|
+
(sum, stats) => sum + (stats.groupingStats?.totalGroups || 0),
|
|
300
|
+
0,
|
|
301
|
+
);
|
|
302
|
+
const totalGroupsWithMultipleTests = allSuiteStats.reduce(
|
|
303
|
+
(sum, stats) =>
|
|
304
|
+
sum + (stats.groupingStats?.groupsWithMultipleTests || 0),
|
|
305
|
+
0,
|
|
306
|
+
);
|
|
307
|
+
const totalTestsInGroups = allSuiteStats.reduce(
|
|
308
|
+
(sum, stats) =>
|
|
309
|
+
sum + (stats.groupingStats?.totalTestsInGroups || 0),
|
|
310
|
+
0,
|
|
311
|
+
);
|
|
312
|
+
const avgTestsPerGroup =
|
|
313
|
+
totalGroups > 0 ? totalTestsInGroups / totalGroups : 0;
|
|
314
|
+
|
|
315
|
+
console.log(`${TTY_COLORS.fg.cyan}${separator}`);
|
|
316
|
+
console.log(
|
|
317
|
+
`${TTY_COLORS.fg.cyan}${TTY_COLORS.bright}TOTALS${TTY_COLORS.reset}`,
|
|
318
|
+
);
|
|
319
|
+
console.log(
|
|
320
|
+
` Total Tests: ${TTY_COLORS.fg.green}${totalTests}${TTY_COLORS.reset}`,
|
|
321
|
+
);
|
|
322
|
+
console.log(
|
|
323
|
+
` Total Migrations: ${TTY_COLORS.fg.yellow}${totalMigrations}${TTY_COLORS.reset} (avg: ${avgMigrationTime}ms)`,
|
|
324
|
+
);
|
|
325
|
+
console.log(
|
|
326
|
+
` Total Migration Time: ${TTY_COLORS.fg.yellow}${totalMigrationTime.toFixed(2)}ms${TTY_COLORS.reset}`,
|
|
327
|
+
);
|
|
328
|
+
console.log(
|
|
329
|
+
` Total Duration: ${TTY_COLORS.fg.blue}${totalDuration.toFixed(2)}ms${TTY_COLORS.reset}`,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Display total grouping statistics
|
|
333
|
+
if (totalGroups > 0) {
|
|
334
|
+
console.log(
|
|
335
|
+
` Total Test Groups: ${TTY_COLORS.fg.cyan}${totalGroups}${TTY_COLORS.reset}`,
|
|
336
|
+
);
|
|
337
|
+
console.log(
|
|
338
|
+
` Avg Tests/Group: ${TTY_COLORS.fg.cyan}${avgTestsPerGroup.toFixed(2)}${TTY_COLORS.reset}`,
|
|
339
|
+
);
|
|
340
|
+
console.log(
|
|
341
|
+
` Groups w/ Multiple Tests: ${TTY_COLORS.fg.cyan}${totalGroupsWithMultipleTests}${TTY_COLORS.reset}`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log(
|
|
346
|
+
`${TTY_COLORS.fg.cyan}${separator}${TTY_COLORS.reset}\n`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await onFinish?.();
|
|
351
|
+
}, 60000);
|
|
352
|
+
|
|
353
|
+
for (const testSuite of tests) {
|
|
354
|
+
await testSuite({
|
|
355
|
+
adapter: async () => {
|
|
356
|
+
await refreshAdapter(betterAuthOptions);
|
|
357
|
+
return adapter;
|
|
358
|
+
},
|
|
359
|
+
adapterDisplayName,
|
|
360
|
+
log,
|
|
361
|
+
getBetterAuthOptions: () => betterAuthOptions,
|
|
362
|
+
modifyBetterAuthOptions: async (options) => {
|
|
363
|
+
const newOptions = deepmerge(defaultBAOptions, options);
|
|
364
|
+
betterAuthOptions = deepmerge(
|
|
365
|
+
newOptions,
|
|
366
|
+
overrideBetterAuthOptions?.(newOptions) || {},
|
|
367
|
+
);
|
|
368
|
+
await refreshAdapter(betterAuthOptions);
|
|
369
|
+
return betterAuthOptions;
|
|
370
|
+
},
|
|
371
|
+
cleanup,
|
|
372
|
+
prefixTests,
|
|
373
|
+
runMigrations: migrate,
|
|
374
|
+
onTestFinish: async (stats: TestSuiteStats) => {
|
|
375
|
+
allSuiteStats.push(stats);
|
|
376
|
+
},
|
|
377
|
+
customIdGenerator,
|
|
378
|
+
transformIdOutput,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
};
|
package/tsconfig.json
ADDED
package/tsdown.config.ts
ADDED