@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.
@@ -0,0 +1,75 @@
1
+ import { createTestSuite } from "./create-test-suite.mjs";
2
+ import { DBAdapter } from "@better-auth/core/db/adapter";
3
+ import { Awaitable, BetterAuthOptions } from "@better-auth/core";
4
+
5
+ //#region src/adapter/test-adapter.d.ts
6
+ type Logger = {
7
+ info: (...args: any[]) => void;
8
+ success: (...args: any[]) => void;
9
+ warn: (...args: any[]) => void;
10
+ error: (...args: any[]) => void;
11
+ debug: (...args: any[]) => void;
12
+ };
13
+ declare const testAdapter: ({
14
+ adapter: getAdapter,
15
+ runMigrations,
16
+ overrideBetterAuthOptions,
17
+ additionalCleanups,
18
+ tests,
19
+ prefixTests,
20
+ onFinish,
21
+ customIdGenerator,
22
+ transformIdOutput
23
+ }: {
24
+ /**
25
+ * A function that will return the adapter instance to test with.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * testAdapter({
30
+ * adapter: (options) => drizzleAdapter(drizzle(db), {
31
+ * schema: generateSchema(options),
32
+ * }),
33
+ * })
34
+ */
35
+ adapter: (options: BetterAuthOptions) => Awaitable<(options: BetterAuthOptions) => DBAdapter<BetterAuthOptions>>;
36
+ /**
37
+ * A function that will run the database migrations.
38
+ */
39
+ runMigrations: (betterAuthOptions: BetterAuthOptions) => Promise<void> | void;
40
+ /**
41
+ * Any potential better-auth options overrides.
42
+ */
43
+ overrideBetterAuthOptions?: (betterAuthOptions: BetterAuthOptions) => BetterAuthOptions;
44
+ /**
45
+ * By default we will cleanup all tables automatically,
46
+ * but if you have additional cleanup logic, you can pass it here.
47
+ *
48
+ * Such as deleting a DB file that could had been created.
49
+ */
50
+ additionalCleanups?: () => Promise<void> | void;
51
+ /**
52
+ * A test suite to run.
53
+ */
54
+ tests: ReturnType<ReturnType<typeof createTestSuite>>[];
55
+ /**
56
+ * A prefix to add to the test suite name.
57
+ */
58
+ prefixTests?: string;
59
+ /**
60
+ * Upon finish of the tests, this function will be called.
61
+ */
62
+ onFinish?: () => Promise<void> | void;
63
+ /**
64
+ * Custom ID generator function to be used by the helper functions. (such as `insertRandom`)
65
+ */
66
+ customIdGenerator?: () => any;
67
+ /**
68
+ * A function that will transform the ID output.
69
+ */
70
+ transformIdOutput?: (id: any) => any;
71
+ }) => Promise<{
72
+ execute: () => void;
73
+ }>;
74
+ //#endregion
75
+ export { Logger, testAdapter };
@@ -0,0 +1,162 @@
1
+ import { deepmerge, initGetModelName } from "@better-auth/core/db/adapter";
2
+ import { TTY_COLORS } from "@better-auth/core/env";
3
+ import { afterAll, beforeAll, describe } from "vitest";
4
+ import { getAuthTables } from "better-auth/db";
5
+
6
+ //#region src/adapter/test-adapter.ts
7
+ const testAdapter = async ({ adapter: getAdapter, runMigrations, overrideBetterAuthOptions, additionalCleanups, tests, prefixTests, onFinish, customIdGenerator, transformIdOutput }) => {
8
+ const defaultBAOptions = {};
9
+ let betterAuthOptions = {
10
+ ...defaultBAOptions,
11
+ ...overrideBetterAuthOptions?.(defaultBAOptions) || {}
12
+ };
13
+ let adapter = (await getAdapter(betterAuthOptions))(betterAuthOptions);
14
+ const adapterName = adapter.options?.adapterConfig.adapterName;
15
+ const adapterId = adapter.options?.adapterConfig.adapterId || adapter.id;
16
+ const adapterDisplayName = adapterName || adapterId;
17
+ const refreshAdapter = async (betterAuthOptions$1) => {
18
+ adapter = (await getAdapter(betterAuthOptions$1))(betterAuthOptions$1);
19
+ };
20
+ /**
21
+ * A helper function to log to the console.
22
+ */
23
+ const log = {
24
+ info: (...args) => console.log(`${TTY_COLORS.fg.blue}INFO ${TTY_COLORS.reset} [${adapterDisplayName}]`, ...args),
25
+ success: (...args) => console.log(`${TTY_COLORS.fg.green}SUCCESS${TTY_COLORS.reset} [${adapterDisplayName}]`, ...args),
26
+ warn: (...args) => console.log(`${TTY_COLORS.fg.yellow}WARN ${TTY_COLORS.reset} [${adapterDisplayName}]`, ...args),
27
+ error: (...args) => console.log(`${TTY_COLORS.fg.red}ERROR ${TTY_COLORS.reset} [${adapterDisplayName}]`, ...args),
28
+ debug: (...args) => console.log(`${TTY_COLORS.fg.magenta}DEBUG ${TTY_COLORS.reset} [${adapterDisplayName}]`, ...args)
29
+ };
30
+ /**
31
+ * Cleanup function to remove all rows from the database.
32
+ */
33
+ const cleanup = async () => {
34
+ const start = performance.now();
35
+ await refreshAdapter(betterAuthOptions);
36
+ const getAllModels = getAuthTables(betterAuthOptions);
37
+ for (const model of Object.keys(getAllModels)) {
38
+ const getModelName = initGetModelName({
39
+ usePlural: adapter.options?.adapterConfig?.usePlural,
40
+ schema: getAllModels
41
+ });
42
+ try {
43
+ const modelName = getModelName(model);
44
+ await adapter.deleteMany({
45
+ model: modelName,
46
+ where: []
47
+ });
48
+ } catch (error) {
49
+ const msg = `Error while cleaning up all rows from ${model}`;
50
+ log.error(msg, error);
51
+ throw new Error(msg, { cause: error });
52
+ }
53
+ }
54
+ try {
55
+ await additionalCleanups?.();
56
+ } catch (error) {
57
+ const msg = `Error while running additional cleanups`;
58
+ log.error(msg, error);
59
+ throw new Error(msg, { cause: error });
60
+ }
61
+ await refreshAdapter(betterAuthOptions);
62
+ log.success(`${TTY_COLORS.bright}CLEAN-UP${TTY_COLORS.reset} completed successfully (${(performance.now() - start).toFixed(3)}ms)`);
63
+ };
64
+ /**
65
+ * A function that will run the database migrations.
66
+ */
67
+ const migrate = async () => {
68
+ const start = performance.now();
69
+ try {
70
+ await runMigrations(betterAuthOptions);
71
+ } catch (error) {
72
+ const msg = `Error while running migrations`;
73
+ log.error(msg, error);
74
+ throw new Error(msg, { cause: error });
75
+ }
76
+ log.success(`${TTY_COLORS.bright}MIGRATIONS${TTY_COLORS.reset} completed successfully (${(performance.now() - start).toFixed(3)}ms)`);
77
+ };
78
+ return { execute: () => {
79
+ describe(adapterDisplayName, async () => {
80
+ const allSuiteStats = [];
81
+ beforeAll(async () => {
82
+ await migrate();
83
+ }, 6e4);
84
+ afterAll(async () => {
85
+ await cleanup();
86
+ if (allSuiteStats.length > 0) {
87
+ const totalMigrations = allSuiteStats.reduce((sum, stats) => sum + stats.migrationCount, 0);
88
+ const totalMigrationTime = allSuiteStats.reduce((sum, stats) => sum + stats.totalMigrationTime, 0);
89
+ const totalTests = allSuiteStats.reduce((sum, stats) => sum + stats.testCount, 0);
90
+ const totalDuration = allSuiteStats.reduce((sum, stats) => sum + stats.suiteDuration, 0);
91
+ const separator = `${"─".repeat(80)}`;
92
+ console.log(`\n${TTY_COLORS.fg.cyan}${separator}`);
93
+ console.log(`${TTY_COLORS.fg.cyan}${TTY_COLORS.bright}TEST SUITE STATISTICS SUMMARY${TTY_COLORS.reset}`);
94
+ console.log(`${TTY_COLORS.fg.cyan}${separator}${TTY_COLORS.reset}\n`);
95
+ for (const stats of allSuiteStats) {
96
+ const avgMigrationTime$1 = stats.migrationCount > 0 ? (stats.totalMigrationTime / stats.migrationCount).toFixed(2) : "0.00";
97
+ console.log(`${TTY_COLORS.fg.magenta}${stats.suiteName}${TTY_COLORS.reset}:`);
98
+ console.log(` Tests: ${TTY_COLORS.fg.green}${stats.testCount}${TTY_COLORS.reset}`);
99
+ console.log(` Migrations: ${TTY_COLORS.fg.yellow}${stats.migrationCount}${TTY_COLORS.reset} (avg: ${avgMigrationTime$1}ms)`);
100
+ console.log(` Total Migration Time: ${TTY_COLORS.fg.yellow}${stats.totalMigrationTime.toFixed(2)}ms${TTY_COLORS.reset}`);
101
+ console.log(` Suite Duration: ${TTY_COLORS.fg.blue}${stats.suiteDuration.toFixed(2)}ms${TTY_COLORS.reset}`);
102
+ if (stats.groupingStats) {
103
+ const { totalGroups: totalGroups$1, averageTestsPerGroup, largestGroupSize, smallestGroupSize, groupsWithMultipleTests } = stats.groupingStats;
104
+ console.log(` Test Groups: ${TTY_COLORS.fg.cyan}${totalGroups$1}${TTY_COLORS.reset}`);
105
+ if (totalGroups$1 > 0) {
106
+ console.log(` Avg Tests/Group: ${TTY_COLORS.fg.cyan}${averageTestsPerGroup.toFixed(2)}${TTY_COLORS.reset}`);
107
+ console.log(` Largest Group: ${TTY_COLORS.fg.cyan}${largestGroupSize}${TTY_COLORS.reset}`);
108
+ console.log(` Smallest Group: ${TTY_COLORS.fg.cyan}${smallestGroupSize}${TTY_COLORS.reset}`);
109
+ console.log(` Groups w/ Multiple Tests: ${TTY_COLORS.fg.cyan}${groupsWithMultipleTests}${TTY_COLORS.reset}`);
110
+ }
111
+ }
112
+ console.log("");
113
+ }
114
+ const avgMigrationTime = totalMigrations > 0 ? (totalMigrationTime / totalMigrations).toFixed(2) : "0.00";
115
+ const totalGroups = allSuiteStats.reduce((sum, stats) => sum + (stats.groupingStats?.totalGroups || 0), 0);
116
+ const totalGroupsWithMultipleTests = allSuiteStats.reduce((sum, stats) => sum + (stats.groupingStats?.groupsWithMultipleTests || 0), 0);
117
+ const totalTestsInGroups = allSuiteStats.reduce((sum, stats) => sum + (stats.groupingStats?.totalTestsInGroups || 0), 0);
118
+ const avgTestsPerGroup = totalGroups > 0 ? totalTestsInGroups / totalGroups : 0;
119
+ console.log(`${TTY_COLORS.fg.cyan}${separator}`);
120
+ console.log(`${TTY_COLORS.fg.cyan}${TTY_COLORS.bright}TOTALS${TTY_COLORS.reset}`);
121
+ console.log(` Total Tests: ${TTY_COLORS.fg.green}${totalTests}${TTY_COLORS.reset}`);
122
+ console.log(` Total Migrations: ${TTY_COLORS.fg.yellow}${totalMigrations}${TTY_COLORS.reset} (avg: ${avgMigrationTime}ms)`);
123
+ console.log(` Total Migration Time: ${TTY_COLORS.fg.yellow}${totalMigrationTime.toFixed(2)}ms${TTY_COLORS.reset}`);
124
+ console.log(` Total Duration: ${TTY_COLORS.fg.blue}${totalDuration.toFixed(2)}ms${TTY_COLORS.reset}`);
125
+ if (totalGroups > 0) {
126
+ console.log(` Total Test Groups: ${TTY_COLORS.fg.cyan}${totalGroups}${TTY_COLORS.reset}`);
127
+ console.log(` Avg Tests/Group: ${TTY_COLORS.fg.cyan}${avgTestsPerGroup.toFixed(2)}${TTY_COLORS.reset}`);
128
+ console.log(` Groups w/ Multiple Tests: ${TTY_COLORS.fg.cyan}${totalGroupsWithMultipleTests}${TTY_COLORS.reset}`);
129
+ }
130
+ console.log(`${TTY_COLORS.fg.cyan}${separator}${TTY_COLORS.reset}\n`);
131
+ }
132
+ await onFinish?.();
133
+ }, 6e4);
134
+ for (const testSuite of tests) await testSuite({
135
+ adapter: async () => {
136
+ await refreshAdapter(betterAuthOptions);
137
+ return adapter;
138
+ },
139
+ adapterDisplayName,
140
+ log,
141
+ getBetterAuthOptions: () => betterAuthOptions,
142
+ modifyBetterAuthOptions: async (options) => {
143
+ const newOptions = deepmerge(defaultBAOptions, options);
144
+ betterAuthOptions = deepmerge(newOptions, overrideBetterAuthOptions?.(newOptions) || {});
145
+ await refreshAdapter(betterAuthOptions);
146
+ return betterAuthOptions;
147
+ },
148
+ cleanup,
149
+ prefixTests,
150
+ runMigrations: migrate,
151
+ onTestFinish: async (stats) => {
152
+ allSuiteStats.push(stats);
153
+ },
154
+ customIdGenerator,
155
+ transformIdOutput
156
+ });
157
+ });
158
+ } };
159
+ };
160
+
161
+ //#endregion
162
+ export { testAdapter };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@better-auth/test-utils",
3
+ "version": "1.5.0-beta.9",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/better-auth/better-auth.git"
8
+ },
9
+ "exports": {
10
+ "./adapter": {
11
+ "dev-source": "./src/adapter.ts",
12
+ "default": "./dist/adapter.mjs",
13
+ "types": "./dist/adapter.d.mts"
14
+ }
15
+ },
16
+ "devDependencies": {
17
+ "tsdown": "^0.19.0",
18
+ "vitest": "^4.0.17",
19
+ "@better-auth/core": "1.5.0-beta.9",
20
+ "better-auth": "1.5.0-beta.9"
21
+ },
22
+ "peerDependencies": {
23
+ "vitest": "^4.0.17",
24
+ "@better-auth/core": "1.5.0-beta.9",
25
+ "better-auth": "1.5.0-beta.9"
26
+ },
27
+ "scripts": {
28
+ "build": "tsdown",
29
+ "dev": "tsdown --watch"
30
+ }
31
+ }