bunderstack 0.21.1 → 0.22.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/CHANGELOG.md +55 -0
- package/README.md +15 -10
- package/dist/api/builder.d.ts +2 -2
- package/dist/api/builder.js +1 -1
- package/dist/api/builder.js.map +1 -1
- package/dist/api/context.d.ts +4 -1
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +2 -0
- package/dist/api/context.js.map +1 -1
- package/dist/auth.d.ts +8 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +14 -0
- package/dist/auth.js.map +1 -1
- package/dist/backend-internals.d.ts +43 -0
- package/dist/backend-internals.d.ts.map +1 -0
- package/dist/backend-internals.js +2 -0
- package/dist/backend-internals.js.map +1 -0
- package/dist/backend.d.ts +26 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +58 -0
- package/dist/backend.js.map +1 -0
- package/dist/blueprint-generator.d.ts.map +1 -1
- package/dist/blueprint-generator.js +41 -51
- package/dist/blueprint-generator.js.map +1 -1
- package/dist/config.d.ts +0 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/database/adapter.d.ts +10 -3
- package/dist/database/adapter.d.ts.map +1 -1
- package/dist/database/adapter.js.map +1 -1
- package/dist/database/bun-sql.d.ts.map +1 -1
- package/dist/database/bun-sql.js +1 -3
- package/dist/database/bun-sql.js.map +1 -1
- package/dist/database/bun-sqlite.d.ts +8 -0
- package/dist/database/bun-sqlite.d.ts.map +1 -0
- package/dist/database/bun-sqlite.js +56 -0
- package/dist/database/bun-sqlite.js.map +1 -0
- package/dist/database/libsql.d.ts.map +1 -1
- package/dist/database/libsql.js +25 -3
- package/dist/database/libsql.js.map +1 -1
- package/dist/database/pglite.d.ts.map +1 -1
- package/dist/database/pglite.js +25 -4
- package/dist/database/pglite.js.map +1 -1
- package/dist/database/postgres-js.d.ts.map +1 -1
- package/dist/database/postgres-js.js +1 -3
- package/dist/database/postgres-js.js.map +1 -1
- package/dist/db.d.ts +1 -2
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +5 -3
- package/dist/db.js.map +1 -1
- package/dist/email.d.ts +3 -1
- package/dist/email.d.ts.map +1 -1
- package/dist/email.js +9 -2
- package/dist/email.js.map +1 -1
- package/dist/env.d.ts +1 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +1 -4
- package/dist/env.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -108
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -484
- package/dist/index.js.map +1 -1
- package/dist/jobs/define.d.ts +1 -1
- package/dist/jobs/define.d.ts.map +1 -1
- package/dist/jobs/define.js.map +1 -1
- package/dist/jobs/index.js +1 -1
- package/dist/jobs/index.js.map +1 -1
- package/dist/jobs/queue.d.ts +1 -1
- package/dist/jobs/queue.d.ts.map +1 -1
- package/dist/jobs/queue.js +1 -2
- package/dist/jobs/queue.js.map +1 -1
- package/dist/jobs/worker.d.ts +21 -0
- package/dist/jobs/worker.d.ts.map +1 -1
- package/dist/jobs/worker.js +64 -4
- package/dist/jobs/worker.js.map +1 -1
- package/dist/logging.d.ts +8 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +6 -0
- package/dist/logging.js.map +1 -0
- package/dist/provision-internals.d.ts +1 -1
- package/dist/provision-internals.js +1 -1
- package/dist/provision-internals.js.map +1 -1
- package/dist/provision.d.ts +2 -0
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +24 -3
- package/dist/provision.js.map +1 -1
- package/dist/query/infer.d.ts +1 -1
- package/dist/query/infer.d.ts.map +1 -1
- package/dist/query/infer.js.map +1 -1
- package/dist/runtime.d.ts +153 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +537 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema-export-pg.js +1 -1
- package/dist/schema-export-pg.js.map +1 -1
- package/dist/storage/background.d.ts +3 -0
- package/dist/storage/background.d.ts.map +1 -0
- package/dist/storage/background.js +3 -0
- package/dist/storage/background.js.map +1 -0
- package/dist/testing/auth.d.ts +85 -0
- package/dist/testing/auth.d.ts.map +1 -0
- package/dist/testing/auth.js +140 -0
- package/dist/testing/auth.js.map +1 -0
- package/dist/testing/client.d.ts +6 -0
- package/dist/testing/client.d.ts.map +1 -0
- package/dist/testing/client.js +9 -0
- package/dist/testing/client.js.map +1 -0
- package/dist/testing/database.d.ts +6 -0
- package/dist/testing/database.d.ts.map +1 -0
- package/dist/testing/database.js +8 -0
- package/dist/testing/database.js.map +1 -0
- package/dist/testing/email.d.ts +15 -0
- package/dist/testing/email.d.ts.map +1 -0
- package/dist/testing/email.js +32 -0
- package/dist/testing/email.js.map +1 -0
- package/dist/testing/fixture.d.ts +51 -0
- package/dist/testing/fixture.d.ts.map +1 -0
- package/dist/testing/fixture.js +178 -0
- package/dist/testing/fixture.js.map +1 -0
- package/dist/testing/jobs.d.ts +40 -0
- package/dist/testing/jobs.d.ts.map +1 -0
- package/dist/testing/jobs.js +76 -0
- package/dist/testing/jobs.js.map +1 -0
- package/dist/testing/logs.d.ts +18 -0
- package/dist/testing/logs.d.ts.map +1 -0
- package/dist/testing/logs.js +53 -0
- package/dist/testing/logs.js.map +1 -0
- package/dist/testing/storage.d.ts +7 -0
- package/dist/testing/storage.d.ts.map +1 -0
- package/dist/testing/storage.js +29 -0
- package/dist/testing/storage.js.map +1 -0
- package/dist/testing.d.ts +10 -26
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +3 -9
- package/dist/testing.js.map +1 -1
- package/llms.txt +51 -14
- package/package.json +23 -15
- package/skills/creating-bunderstack-apps/references/application-structure.md +16 -12
- package/skills/creating-bunderstack-apps/references/runtime-integrations.md +4 -1
- package/skills/migrating-to-bunderstack/SKILL.md +7 -6
- package/skills/migrating-to-bunderstack/references/audit-checklist.md +4 -4
- package/skills/migrating-to-bunderstack/references/runtime-replacements.md +55 -49
package/dist/schema-export-pg.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// src/schema-export-pg.ts — pg twins under the same names bunderstack/schema
|
|
2
|
-
// uses, so `export * from 'bunderstack/schema
|
|
2
|
+
// uses, so `export * from 'bunderstack/schema-pg'` mirrors the sqlite setup.
|
|
3
3
|
export { bunderstackEmailEventsPg as bunderstackEmailEvents, bunderstackEmailsPg as bunderstackEmails, bunderstackFilesPg as bunderstackFiles, bunderstackIdempotencyPg as bunderstackIdempotency, bunderstackJobsPg as bunderstackJobs, } from './internal-tables-pg.js';
|
|
4
4
|
//# sourceMappingURL=schema-export-pg.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-export-pg.js","sourceRoot":"","sources":["../src/schema-export-pg.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,OAAO,EACL,wBAAwB,IAAI,sBAAsB,EAClD,mBAAmB,IAAI,iBAAiB,EACxC,kBAAkB,IAAI,gBAAgB,EACtC,wBAAwB,IAAI,sBAAsB,EAClD,iBAAiB,IAAI,eAAe,GACrC,MAAM,sBAAsB,CAAA","sourcesContent":["// src/schema-export-pg.ts — pg twins under the same names bunderstack/schema\n// uses, so `export * from 'bunderstack/schema
|
|
1
|
+
{"version":3,"file":"schema-export-pg.js","sourceRoot":"","sources":["../src/schema-export-pg.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,OAAO,EACL,wBAAwB,IAAI,sBAAsB,EAClD,mBAAmB,IAAI,iBAAiB,EACxC,kBAAkB,IAAI,gBAAgB,EACtC,wBAAwB,IAAI,sBAAsB,EAClD,iBAAiB,IAAI,eAAe,GACrC,MAAM,sBAAsB,CAAA","sourcesContent":["// src/schema-export-pg.ts — pg twins under the same names bunderstack/schema\n// uses, so `export * from 'bunderstack/schema-pg'` mirrors the sqlite setup.\nexport {\n bunderstackEmailEventsPg as bunderstackEmailEvents,\n bunderstackEmailsPg as bunderstackEmails,\n bunderstackFilesPg as bunderstackFiles,\n bunderstackIdempotencyPg as bunderstackIdempotency,\n bunderstackJobsPg as bunderstackJobs,\n} from './internal-tables-pg'\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../src/storage/background.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,8BAA8B,CAAA;AACjE,eAAO,MAAM,sBAAsB,cAAc,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background.js","sourceRoot":"","sources":["../../src/storage/background.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,2BAA2B,CAAA;AACjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAA","sourcesContent":["export const STORAGE_SWEEP_JOB_NAME = 'bunderstack:storage-sweep'\nexport const STORAGE_SWEEP_SCHEDULE = '0 4 * * *'\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export type TestUser = {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
emailVerified?: boolean;
|
|
6
|
+
image?: string | null;
|
|
7
|
+
createdAt?: string | Date;
|
|
8
|
+
updatedAt?: string | Date;
|
|
9
|
+
};
|
|
10
|
+
export type TestIdentity = {
|
|
11
|
+
user: TestUser;
|
|
12
|
+
headers: Headers;
|
|
13
|
+
};
|
|
14
|
+
export declare class TestAuthError extends Error {
|
|
15
|
+
readonly status: number;
|
|
16
|
+
readonly body: unknown;
|
|
17
|
+
constructor(status: number, body: unknown);
|
|
18
|
+
}
|
|
19
|
+
export type SignUpEmailInput = {
|
|
20
|
+
email: string;
|
|
21
|
+
name: string;
|
|
22
|
+
password?: string;
|
|
23
|
+
};
|
|
24
|
+
export type SignInEmailInput = {
|
|
25
|
+
email: string;
|
|
26
|
+
password?: string;
|
|
27
|
+
};
|
|
28
|
+
export type TestSession = {
|
|
29
|
+
user: TestUser;
|
|
30
|
+
session: {
|
|
31
|
+
activeOrganizationId?: string | null;
|
|
32
|
+
} | null;
|
|
33
|
+
};
|
|
34
|
+
export type TestAuth = {
|
|
35
|
+
signUpEmail(input: SignUpEmailInput): Promise<TestIdentity>;
|
|
36
|
+
signInEmail(input: SignInEmailInput): Promise<TestIdentity>;
|
|
37
|
+
getSession(identity: TestIdentity): Promise<TestSession | null>;
|
|
38
|
+
signOut(identity: TestIdentity): Promise<void>;
|
|
39
|
+
verifyEmail(identity: TestIdentity): Promise<TestIdentity>;
|
|
40
|
+
mockSession<TUser extends TestUser>(user: TUser, session?: {
|
|
41
|
+
activeOrganizationId?: string | null;
|
|
42
|
+
} | null): TestIdentity & {
|
|
43
|
+
user: TUser;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export type AuthSessionResolverLike = {
|
|
47
|
+
api: {
|
|
48
|
+
getSession: (opts: {
|
|
49
|
+
headers: Headers;
|
|
50
|
+
}) => Promise<unknown>;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export type BunderstackAppLike = {
|
|
54
|
+
auth: unknown;
|
|
55
|
+
};
|
|
56
|
+
export type TestSessionRegistry = {
|
|
57
|
+
readonly resolver: AuthSessionResolver;
|
|
58
|
+
mocked(headers: Headers): TestSession | undefined;
|
|
59
|
+
mock<TUser extends TestUser>(user: TUser, session?: {
|
|
60
|
+
activeOrganizationId?: string | null;
|
|
61
|
+
} | null): TestIdentity & {
|
|
62
|
+
user: TUser;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export declare function createTestSessionRegistry(): TestSessionRegistry;
|
|
66
|
+
export declare function mockAuthSession<TUser extends {
|
|
67
|
+
id: string;
|
|
68
|
+
email: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
role?: string;
|
|
71
|
+
}>(app: BunderstackAppLike, resolver: (opts: {
|
|
72
|
+
headers: Headers;
|
|
73
|
+
}) => Promise<{
|
|
74
|
+
user: TUser;
|
|
75
|
+
session?: {
|
|
76
|
+
activeOrganizationId?: string | null;
|
|
77
|
+
} | null;
|
|
78
|
+
} | null>): void;
|
|
79
|
+
export declare function createTestAuth(app: {
|
|
80
|
+
handler(request: Request): Promise<Response>;
|
|
81
|
+
auth: unknown;
|
|
82
|
+
}, email: TestEmail, sessions: TestSessionRegistry): TestAuth;
|
|
83
|
+
import type { AuthSessionResolver } from '../access.js';
|
|
84
|
+
import type { TestEmail } from './email.js';
|
|
85
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/testing/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export class TestAuthError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
body;
|
|
4
|
+
constructor(status, body) {
|
|
5
|
+
super(`Better Auth request failed (${status})`);
|
|
6
|
+
this.name = 'TestAuthError';
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const TEST_SESSION_HEADER = 'x-bunderstack-test-session';
|
|
12
|
+
export function createTestSessionRegistry() {
|
|
13
|
+
const sessions = new Map();
|
|
14
|
+
return {
|
|
15
|
+
resolver: {
|
|
16
|
+
api: {
|
|
17
|
+
async getSession({ headers }) {
|
|
18
|
+
const token = headers.get(TEST_SESSION_HEADER);
|
|
19
|
+
if (token)
|
|
20
|
+
return sessions.get(token) ?? null;
|
|
21
|
+
return null;
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
mocked(headers) {
|
|
26
|
+
const token = headers.get(TEST_SESSION_HEADER);
|
|
27
|
+
return token ? sessions.get(token) : undefined;
|
|
28
|
+
},
|
|
29
|
+
mock(user, session = null) {
|
|
30
|
+
const token = crypto.randomUUID();
|
|
31
|
+
sessions.set(token, { user, session });
|
|
32
|
+
return {
|
|
33
|
+
user,
|
|
34
|
+
headers: new Headers({ [TEST_SESSION_HEADER]: token }),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function mockAuthSession(app, resolver) {
|
|
40
|
+
const auth = app.auth;
|
|
41
|
+
if (auth?.api) {
|
|
42
|
+
auth.api.getSession = resolver;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function cookieHeaders(response) {
|
|
46
|
+
const values = response.headers.getSetCookie();
|
|
47
|
+
const cookie = values
|
|
48
|
+
.map((value) => value.split(';', 1)[0])
|
|
49
|
+
.filter((value) => Boolean(value))
|
|
50
|
+
.join('; ');
|
|
51
|
+
return new Headers(cookie ? { cookie } : undefined);
|
|
52
|
+
}
|
|
53
|
+
export function createTestAuth(app, email, sessions) {
|
|
54
|
+
const request = async (path, init = {}, headers) => {
|
|
55
|
+
const mergedHeaders = new Headers(headers);
|
|
56
|
+
if (init.body !== undefined && !mergedHeaders.has('content-type')) {
|
|
57
|
+
mergedHeaders.set('content-type', 'application/json');
|
|
58
|
+
}
|
|
59
|
+
return app.handler(new Request(new URL(path, 'http://bunderstack.test'), {
|
|
60
|
+
...init,
|
|
61
|
+
headers: mergedHeaders,
|
|
62
|
+
}));
|
|
63
|
+
};
|
|
64
|
+
const identityFromResponse = async (response) => {
|
|
65
|
+
const body = await response.json().catch(() => null);
|
|
66
|
+
if (!response.ok)
|
|
67
|
+
throw new TestAuthError(response.status, body);
|
|
68
|
+
const user = body?.user;
|
|
69
|
+
if (!user)
|
|
70
|
+
throw new TestAuthError(response.status, body);
|
|
71
|
+
return { user, headers: cookieHeaders(response) };
|
|
72
|
+
};
|
|
73
|
+
const currentSession = async (identity) => {
|
|
74
|
+
const mocked = sessions.mocked(identity.headers);
|
|
75
|
+
if (mocked)
|
|
76
|
+
return mocked;
|
|
77
|
+
const auth = app.auth;
|
|
78
|
+
const result = await auth.api.getSession({ headers: identity.headers });
|
|
79
|
+
if (!result || typeof result !== 'object' || !('user' in result)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const value = result;
|
|
83
|
+
return value.user
|
|
84
|
+
? { user: value.user, session: value.session ?? null }
|
|
85
|
+
: null;
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
async signUpEmail(input) {
|
|
89
|
+
return identityFromResponse(await request('/api/auth/sign-up/email', {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
email: input.email,
|
|
93
|
+
name: input.name,
|
|
94
|
+
password: input.password ?? 'password-123',
|
|
95
|
+
}),
|
|
96
|
+
}));
|
|
97
|
+
},
|
|
98
|
+
async signInEmail(input) {
|
|
99
|
+
return identityFromResponse(await request('/api/auth/sign-in/email', {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
email: input.email,
|
|
103
|
+
password: input.password ?? 'password-123',
|
|
104
|
+
}),
|
|
105
|
+
}));
|
|
106
|
+
},
|
|
107
|
+
async getSession(identity) {
|
|
108
|
+
return currentSession(identity);
|
|
109
|
+
},
|
|
110
|
+
async signOut(identity) {
|
|
111
|
+
const response = await request('/api/auth/sign-out', { method: 'POST' }, identity.headers);
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new TestAuthError(response.status, await response.json().catch(() => null));
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
async verifyEmail(identity) {
|
|
117
|
+
const message = [...email.sent]
|
|
118
|
+
.reverse()
|
|
119
|
+
.find((candidate) => candidate.to.includes(identity.user.email));
|
|
120
|
+
const content = `${message?.text ?? ''}\n${message?.html ?? ''}`;
|
|
121
|
+
const url = content.match(/https?:\/\/[^\s<>"']+\/api\/auth\/verify-email\?[^\s<>"']+/)?.[0];
|
|
122
|
+
if (!url) {
|
|
123
|
+
throw new Error(`[bunderstack] no verification link captured for ${identity.user.email}`);
|
|
124
|
+
}
|
|
125
|
+
const response = await request(url, {}, identity.headers);
|
|
126
|
+
if (response.status >= 400) {
|
|
127
|
+
throw new TestAuthError(response.status, await response.text());
|
|
128
|
+
}
|
|
129
|
+
const user = (await currentSession(identity))?.user;
|
|
130
|
+
if (!user) {
|
|
131
|
+
throw new Error('[bunderstack] verified identity has no active session');
|
|
132
|
+
}
|
|
133
|
+
return { user, headers: identity.headers };
|
|
134
|
+
},
|
|
135
|
+
mockSession(user, session) {
|
|
136
|
+
return sessions.mock(user, session);
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/testing/auth.ts"],"names":[],"mappings":"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","sourcesContent":["export type TestUser = {\n id: string\n email: string\n name: string\n emailVerified?: boolean\n image?: string | null\n createdAt?: string | Date\n updatedAt?: string | Date\n}\n\nexport type TestIdentity = {\n user: TestUser\n headers: Headers\n}\n\nexport class TestAuthError extends Error {\n readonly status: number\n readonly body: unknown\n\n constructor(status: number, body: unknown) {\n super(`Better Auth request failed (${status})`)\n this.name = 'TestAuthError'\n this.status = status\n this.body = body\n }\n}\n\nexport type SignUpEmailInput = {\n email: string\n name: string\n password?: string\n}\n\nexport type SignInEmailInput = {\n email: string\n password?: string\n}\n\nexport type TestSession = {\n user: TestUser\n session: { activeOrganizationId?: string | null } | null\n}\n\nexport type TestAuth = {\n signUpEmail(input: SignUpEmailInput): Promise<TestIdentity>\n signInEmail(input: SignInEmailInput): Promise<TestIdentity>\n getSession(identity: TestIdentity): Promise<TestSession | null>\n signOut(identity: TestIdentity): Promise<void>\n verifyEmail(identity: TestIdentity): Promise<TestIdentity>\n mockSession<TUser extends TestUser>(\n user: TUser,\n session?: { activeOrganizationId?: string | null } | null,\n ): TestIdentity & { user: TUser }\n}\n\nexport type AuthSessionResolverLike = {\n api: {\n getSession: (opts: { headers: Headers }) => Promise<unknown>\n }\n}\n\nexport type BunderstackAppLike = {\n auth: unknown\n}\n\nconst TEST_SESSION_HEADER = 'x-bunderstack-test-session'\n\nexport type TestSessionRegistry = {\n readonly resolver: AuthSessionResolver\n mocked(headers: Headers): TestSession | undefined\n mock<TUser extends TestUser>(\n user: TUser,\n session?: { activeOrganizationId?: string | null } | null,\n ): TestIdentity & { user: TUser }\n}\n\nexport function createTestSessionRegistry(): TestSessionRegistry {\n const sessions = new Map<string, TestSession>()\n return {\n resolver: {\n api: {\n async getSession({ headers }) {\n const token = headers.get(TEST_SESSION_HEADER)\n if (token) return sessions.get(token) ?? null\n return null\n },\n },\n },\n mocked(headers) {\n const token = headers.get(TEST_SESSION_HEADER)\n return token ? sessions.get(token) : undefined\n },\n mock(user, session = null) {\n const token = crypto.randomUUID()\n sessions.set(token, { user, session })\n return {\n user,\n headers: new Headers({ [TEST_SESSION_HEADER]: token }),\n }\n },\n }\n}\n\nexport function mockAuthSession<\n TUser extends { id: string; email: string; name?: string; role?: string },\n>(\n app: BunderstackAppLike,\n resolver: (opts: { headers: Headers }) => Promise<{\n user: TUser\n session?: { activeOrganizationId?: string | null } | null\n } | null>,\n): void {\n const auth = app.auth as unknown as AuthSessionResolverLike\n if (auth?.api) {\n auth.api.getSession = resolver as unknown as (opts: {\n headers: Headers\n }) => Promise<unknown>\n }\n}\n\nfunction cookieHeaders(response: Response): Headers {\n const values = response.headers.getSetCookie()\n const cookie = values\n .map((value) => value.split(';', 1)[0])\n .filter((value): value is string => Boolean(value))\n .join('; ')\n return new Headers(cookie ? { cookie } : undefined)\n}\n\nexport function createTestAuth(\n app: {\n handler(request: Request): Promise<Response>\n auth: unknown\n },\n email: TestEmail,\n sessions: TestSessionRegistry,\n): TestAuth {\n const request = async (\n path: string,\n init: RequestInit = {},\n headers?: Headers,\n ) => {\n const mergedHeaders = new Headers(headers)\n if (init.body !== undefined && !mergedHeaders.has('content-type')) {\n mergedHeaders.set('content-type', 'application/json')\n }\n return app.handler(\n new Request(new URL(path, 'http://bunderstack.test'), {\n ...init,\n headers: mergedHeaders,\n }),\n )\n }\n\n const identityFromResponse = async (response: Response) => {\n const body = await response.json().catch(() => null)\n if (!response.ok) throw new TestAuthError(response.status, body)\n const user = (body as { user?: TestUser } | null)?.user\n if (!user) throw new TestAuthError(response.status, body)\n return { user, headers: cookieHeaders(response) }\n }\n\n const currentSession = async (\n identity: TestIdentity,\n ): Promise<TestSession | null> => {\n const mocked = sessions.mocked(identity.headers)\n if (mocked) return mocked\n const auth = app.auth as {\n api: {\n getSession(options: { headers: Headers }): Promise<unknown>\n }\n }\n const result = await auth.api.getSession({ headers: identity.headers })\n if (!result || typeof result !== 'object' || !('user' in result)) {\n return null\n }\n const value = result as {\n user?: TestUser\n session?: TestSession['session']\n }\n return value.user\n ? { user: value.user, session: value.session ?? null }\n : null\n }\n\n return {\n async signUpEmail(input) {\n return identityFromResponse(\n await request('/api/auth/sign-up/email', {\n method: 'POST',\n body: JSON.stringify({\n email: input.email,\n name: input.name,\n password: input.password ?? 'password-123',\n }),\n }),\n )\n },\n async signInEmail(input) {\n return identityFromResponse(\n await request('/api/auth/sign-in/email', {\n method: 'POST',\n body: JSON.stringify({\n email: input.email,\n password: input.password ?? 'password-123',\n }),\n }),\n )\n },\n async getSession(identity) {\n return currentSession(identity)\n },\n async signOut(identity) {\n const response = await request(\n '/api/auth/sign-out',\n { method: 'POST' },\n identity.headers,\n )\n if (!response.ok) {\n throw new TestAuthError(\n response.status,\n await response.json().catch(() => null),\n )\n }\n },\n async verifyEmail(identity) {\n const message = [...email.sent]\n .reverse()\n .find((candidate) => candidate.to.includes(identity.user.email))\n const content = `${message?.text ?? ''}\\n${message?.html ?? ''}`\n const url = content.match(\n /https?:\\/\\/[^\\s<>\"']+\\/api\\/auth\\/verify-email\\?[^\\s<>\"']+/,\n )?.[0]\n if (!url) {\n throw new Error(\n `[bunderstack] no verification link captured for ${identity.user.email}`,\n )\n }\n const response = await request(url, {}, identity.headers)\n if (response.status >= 400) {\n throw new TestAuthError(response.status, await response.text())\n }\n const user = (await currentSession(identity))?.user\n if (!user) {\n throw new Error('[bunderstack] verified identity has no active session')\n }\n return { user, headers: identity.headers }\n },\n mockSession(user, session) {\n return sessions.mock(user, session)\n },\n }\n}\nimport type { AuthSessionResolver } from '../access'\nimport type { TestEmail } from './email'\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnyBunderstackApp, BunderstackClient } from '../client/rpc-client.js';
|
|
2
|
+
import type { TestIdentity } from './auth.js';
|
|
3
|
+
export declare function testClient<TApp extends AnyBunderstackApp>(app: TApp & {
|
|
4
|
+
handler(request: Request): Promise<Response>;
|
|
5
|
+
}, identity?: TestIdentity): BunderstackClient<TApp>;
|
|
6
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAI1C,wBAAgB,UAAU,CAAC,IAAI,SAAS,iBAAiB,EACvD,GAAG,EAAE,IAAI,GAAG;IAAE,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,EAC5D,QAAQ,CAAC,EAAE,YAAY,GACtB,iBAAiB,CAAC,IAAI,CAAC,CAMzB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createClient } from '../client/rpc-client.js';
|
|
2
|
+
export function testClient(app, identity) {
|
|
3
|
+
return createClient({
|
|
4
|
+
baseUrl: 'http://bunderstack.test/api',
|
|
5
|
+
fetch: (input, init) => app.handler(new Request(input, init)),
|
|
6
|
+
headers: identity?.headers,
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD,MAAM,UAAU,UAAU,CACxB,GAA4D,EAC5D,QAAuB;IAEvB,OAAO,YAAY,CAAO;QACxB,OAAO,EAAE,6BAA6B;QACtC,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC7D,OAAO,EAAE,QAAQ,EAAE,OAAO;KAC3B,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { AnyBunderstackApp, BunderstackClient } from '../client/rpc-client'\nimport type { TestIdentity } from './auth'\n\nimport { createClient } from '../client/rpc-client'\n\nexport function testClient<TApp extends AnyBunderstackApp>(\n app: TApp & { handler(request: Request): Promise<Response> },\n identity?: TestIdentity,\n): BunderstackClient<TApp> {\n return createClient<TApp>({\n baseUrl: 'http://bunderstack.test/api',\n fetch: (input, init) => app.handler(new Request(input, init)),\n headers: identity?.headers,\n })\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DatabaseAdapter, TestDatabaseStrategy, TestDatabaseTarget } from '../database/adapter.js';
|
|
2
|
+
export declare function createTestDatabaseTarget(adapter: DatabaseAdapter, options: {
|
|
3
|
+
mode: 'memory' | 'temporary';
|
|
4
|
+
strategy?: TestDatabaseStrategy;
|
|
5
|
+
}): Promise<TestDatabaseTarget>;
|
|
6
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/testing/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,qBAAqB,CAAA;AAE5B,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE;IACP,IAAI,EAAE,QAAQ,GAAG,WAAW,CAAA;IAC5B,QAAQ,CAAC,EAAE,oBAAoB,CAAA;CAChC,GACA,OAAO,CAAC,kBAAkB,CAAC,CAQ7B"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export async function createTestDatabaseTarget(adapter, options) {
|
|
2
|
+
const strategy = options.strategy ?? adapter.testing;
|
|
3
|
+
if (!strategy) {
|
|
4
|
+
throw new Error(`[bunderstack] ${adapter.driver} requires an explicit test database strategy`);
|
|
5
|
+
}
|
|
6
|
+
return strategy.createTarget({ mode: options.mode });
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/testing/database.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAwB,EACxB,OAGC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAA;IACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,CAAC,MAAM,8CAA8C,CAC9E,CAAA;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;AACtD,CAAC","sourcesContent":["import type {\n DatabaseAdapter,\n TestDatabaseStrategy,\n TestDatabaseTarget,\n} from '../database/adapter'\n\nexport async function createTestDatabaseTarget(\n adapter: DatabaseAdapter,\n options: {\n mode: 'memory' | 'temporary'\n strategy?: TestDatabaseStrategy\n },\n): Promise<TestDatabaseTarget> {\n const strategy = options.strategy ?? adapter.testing\n if (!strategy) {\n throw new Error(\n `[bunderstack] ${adapter.driver} requires an explicit test database strategy`,\n )\n }\n return strategy.createTarget({ mode: options.mode })\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EmailAdapter, EmailMessage } from '../email.js';
|
|
2
|
+
export type CapturedEmail = Omit<EmailMessage, 'to' | 'cc' | 'bcc'> & {
|
|
3
|
+
from: string;
|
|
4
|
+
to: string[];
|
|
5
|
+
cc?: string[];
|
|
6
|
+
bcc?: string[];
|
|
7
|
+
};
|
|
8
|
+
export type TestEmail = {
|
|
9
|
+
readonly sent: readonly Readonly<CapturedEmail>[];
|
|
10
|
+
};
|
|
11
|
+
export declare function createTestEmail(): {
|
|
12
|
+
adapter: EmailAdapter;
|
|
13
|
+
email: TestEmail;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=email.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/testing/email.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE1D,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC,GAAG;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,EAAE,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;CACf,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAA;CAClD,CAAA;AAKD,wBAAgB,eAAe,IAAI;IACjC,OAAO,EAAE,YAAY,CAAA;IACrB,KAAK,EAAE,SAAS,CAAA;CACjB,CAiCA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const addresses = (value) => value === undefined ? undefined : Array.isArray(value) ? [...value] : [value];
|
|
2
|
+
export function createTestEmail() {
|
|
3
|
+
const messages = [];
|
|
4
|
+
return {
|
|
5
|
+
adapter: {
|
|
6
|
+
async send(message) {
|
|
7
|
+
messages.push({
|
|
8
|
+
...message,
|
|
9
|
+
to: addresses(message.to) ?? [],
|
|
10
|
+
cc: addresses(message.cc),
|
|
11
|
+
bcc: addresses(message.bcc),
|
|
12
|
+
});
|
|
13
|
+
return { id: `test-email-${messages.length}` };
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
email: {
|
|
17
|
+
get sent() {
|
|
18
|
+
return Object.freeze(messages.map((message) => Object.freeze({
|
|
19
|
+
...message,
|
|
20
|
+
to: Object.freeze([...message.to]),
|
|
21
|
+
cc: message.cc
|
|
22
|
+
? Object.freeze([...message.cc])
|
|
23
|
+
: undefined,
|
|
24
|
+
bcc: message.bcc
|
|
25
|
+
? Object.freeze([...message.bcc])
|
|
26
|
+
: undefined,
|
|
27
|
+
})));
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=email.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/testing/email.ts"],"names":[],"mappings":"AAaA,MAAM,SAAS,GAAG,CAAC,KAAoC,EAAE,EAAE,CACzD,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;AAE/E,MAAM,UAAU,eAAe;IAI7B,MAAM,QAAQ,GAAoB,EAAE,CAAA;IACpC,OAAO;QACL,OAAO,EAAE;YACP,KAAK,CAAC,IAAI,CAAC,OAAO;gBAChB,QAAQ,CAAC,IAAI,CAAC;oBACZ,GAAG,OAAO;oBACV,EAAE,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;oBAC/B,EAAE,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;oBACzB,GAAG,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC;iBAC5B,CAAC,CAAA;gBACF,OAAO,EAAE,EAAE,EAAE,cAAc,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAA;YAChD,CAAC;SACF;QACD,KAAK,EAAE;YACL,IAAI,IAAI;gBACN,OAAO,MAAM,CAAC,MAAM,CAClB,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACvB,MAAM,CAAC,MAAM,CAAC;oBACZ,GAAG,OAAO;oBACV,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,CAAwB;oBACzD,EAAE,EAAE,OAAO,CAAC,EAAE;wBACZ,CAAC,CAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,CAAyB;wBACzD,CAAC,CAAC,SAAS;oBACb,GAAG,EAAE,OAAO,CAAC,GAAG;wBACd,CAAC,CAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAyB;wBAC1D,CAAC,CAAC,SAAS;iBACd,CAAC,CACH,CACF,CAAA;YACH,CAAC;SACF;KACF,CAAA;AACH,CAAC","sourcesContent":["import type { EmailAdapter, EmailMessage } from '../email'\n\nexport type CapturedEmail = Omit<EmailMessage, 'to' | 'cc' | 'bcc'> & {\n from: string\n to: string[]\n cc?: string[]\n bcc?: string[]\n}\n\nexport type TestEmail = {\n readonly sent: readonly Readonly<CapturedEmail>[]\n}\n\nconst addresses = (value: string | string[] | undefined) =>\n value === undefined ? undefined : Array.isArray(value) ? [...value] : [value]\n\nexport function createTestEmail(): {\n adapter: EmailAdapter\n email: TestEmail\n} {\n const messages: CapturedEmail[] = []\n return {\n adapter: {\n async send(message) {\n messages.push({\n ...message,\n to: addresses(message.to) ?? [],\n cc: addresses(message.cc),\n bcc: addresses(message.bcc),\n })\n return { id: `test-email-${messages.length}` }\n },\n },\n email: {\n get sent() {\n return Object.freeze(\n messages.map((message) =>\n Object.freeze({\n ...message,\n to: Object.freeze([...message.to]) as unknown as string[],\n cc: message.cc\n ? (Object.freeze([...message.cc]) as unknown as string[])\n : undefined,\n bcc: message.bcc\n ? (Object.freeze([...message.bcc]) as unknown as string[])\n : undefined,\n }),\n ),\n )\n },\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BunderstackBackend } from '../backend.js';
|
|
2
|
+
import type { AnyBunderstackApp, BunderstackClient } from '../client/rpc-client.js';
|
|
3
|
+
import type { TestDatabaseStrategy } from '../database/adapter.js';
|
|
4
|
+
import type { TestSchemaMode } from '../provision.js';
|
|
5
|
+
import type { TestAuth, TestIdentity } from './auth.js';
|
|
6
|
+
import type { TestEmail } from './email.js';
|
|
7
|
+
import type { TestLogs, TestLogMode } from './logs.js';
|
|
8
|
+
import type { TestStorage } from './storage.js';
|
|
9
|
+
import { type TestJobs } from './jobs.js';
|
|
10
|
+
export type TestOptions = {
|
|
11
|
+
env?: Record<string, string | undefined>;
|
|
12
|
+
database?: {
|
|
13
|
+
mode?: 'memory' | 'temporary';
|
|
14
|
+
schema?: TestSchemaMode;
|
|
15
|
+
strategy?: TestDatabaseStrategy;
|
|
16
|
+
};
|
|
17
|
+
logs?: TestLogMode;
|
|
18
|
+
};
|
|
19
|
+
export type TestCleanup = () => unknown | Promise<unknown>;
|
|
20
|
+
export type TestSetup<TApp, TContext> = (fixture: TestFixture<TApp>) => TContext | Promise<TContext>;
|
|
21
|
+
export type TestConfigureOptions<TApp, TContext> = TestOptions & {
|
|
22
|
+
setup?: TestSetup<TApp, TContext>;
|
|
23
|
+
};
|
|
24
|
+
export type TestFixture<TApp> = AsyncDisposable & {
|
|
25
|
+
readonly app: TApp;
|
|
26
|
+
readonly auth: TestAuth;
|
|
27
|
+
readonly email: TestEmail;
|
|
28
|
+
readonly jobs: TestJobs;
|
|
29
|
+
readonly logs: TestLogs;
|
|
30
|
+
readonly storage: TestStorage;
|
|
31
|
+
defer(cleanup: TestCleanup): void;
|
|
32
|
+
client(identity?: TestIdentity): TApp extends AnyBunderstackApp ? BunderstackClient<TApp> : never;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
export type ConfiguredTestFixture<TApp, TContext> = TestFixture<TApp> & {
|
|
36
|
+
readonly context: TContext;
|
|
37
|
+
};
|
|
38
|
+
export type TestFactory<TApp, TContext> = (options?: TestOptions) => Promise<ConfiguredTestFixture<TApp, TContext>>;
|
|
39
|
+
export type TestMethod<TApp> = {
|
|
40
|
+
(options?: TestOptions): Promise<TestFixture<TApp>>;
|
|
41
|
+
configure<TContext = undefined>(options: TestConfigureOptions<TApp, TContext>): TestFactory<TApp, TContext>;
|
|
42
|
+
};
|
|
43
|
+
type TestableApp = {
|
|
44
|
+
auth: unknown;
|
|
45
|
+
handler(request: Request): Promise<Response>;
|
|
46
|
+
close(): Promise<void>;
|
|
47
|
+
};
|
|
48
|
+
export declare function configureTestApp<TApp extends TestableApp, TContext = undefined>(backend: BunderstackBackend<TApp>, options: TestConfigureOptions<TApp, TContext>): TestFactory<TApp, TContext>;
|
|
49
|
+
export declare function createTestApp<TApp extends TestableApp>(backend: BunderstackBackend<TApp>, options?: TestOptions): Promise<TestFixture<TApp>>;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=fixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { BACKEND_INTERNALS, } from '../backend-internals.js';
|
|
5
|
+
import { provisionForTest } from '../provision.js';
|
|
6
|
+
import { createTestAuth, createTestSessionRegistry } from './auth.js';
|
|
7
|
+
import { testClient } from './client.js';
|
|
8
|
+
import { createTestDatabaseTarget } from './database.js';
|
|
9
|
+
import { createTestEmail } from './email.js';
|
|
10
|
+
import { createTestJobs } from './jobs.js';
|
|
11
|
+
import { createTestLogs } from './logs.js';
|
|
12
|
+
import { createTestStorage, resolveTestBuckets } from './storage.js';
|
|
13
|
+
const defaultTestEnv = {
|
|
14
|
+
NODE_ENV: 'test',
|
|
15
|
+
AUTH_SECRET: 'bunderstack-test-secret',
|
|
16
|
+
BUNDERSTACK_ROLE: 'web',
|
|
17
|
+
};
|
|
18
|
+
function mergeTestOptions(defaults, overrides) {
|
|
19
|
+
return {
|
|
20
|
+
...defaults,
|
|
21
|
+
...overrides,
|
|
22
|
+
env: defaults.env || overrides.env
|
|
23
|
+
? { ...defaults.env, ...overrides.env }
|
|
24
|
+
: undefined,
|
|
25
|
+
database: defaults.database || overrides.database
|
|
26
|
+
? { ...defaults.database, ...overrides.database }
|
|
27
|
+
: undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function configureTestApp(backend, options) {
|
|
31
|
+
const { setup, ...defaults } = options;
|
|
32
|
+
return async (overrides = {}) => {
|
|
33
|
+
const fixture = await createTestApp(backend, mergeTestOptions(defaults, overrides));
|
|
34
|
+
try {
|
|
35
|
+
const context = setup ? await setup(fixture) : undefined;
|
|
36
|
+
return Object.assign(fixture, { context });
|
|
37
|
+
}
|
|
38
|
+
catch (cause) {
|
|
39
|
+
try {
|
|
40
|
+
await fixture.close();
|
|
41
|
+
}
|
|
42
|
+
catch (cleanupCause) {
|
|
43
|
+
throw new AggregateError([cause, cleanupCause], '[bunderstack] test fixture setup and cleanup failed');
|
|
44
|
+
}
|
|
45
|
+
throw cause;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function createTestApp(backend, options = {}) {
|
|
50
|
+
const internals = backend[BACKEND_INTERNALS];
|
|
51
|
+
const storageRoot = await mkdtemp(join(tmpdir(), 'bunderstack-storage-'));
|
|
52
|
+
const resolvedStorage = resolveTestBuckets(internals.declaration.config.storage, storageRoot);
|
|
53
|
+
const { adapter: emailAdapter, email } = createTestEmail();
|
|
54
|
+
const storage = createTestStorage(resolvedStorage);
|
|
55
|
+
const sessions = createTestSessionRegistry();
|
|
56
|
+
const { logger, logs } = createTestLogs(options.logs);
|
|
57
|
+
let target;
|
|
58
|
+
try {
|
|
59
|
+
target = await createTestDatabaseTarget(internals.declaration.config.database.adapter, {
|
|
60
|
+
mode: options.database?.mode ?? 'memory',
|
|
61
|
+
strategy: options.database?.strategy,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (cause) {
|
|
65
|
+
await rm(storageRoot, { recursive: true, force: true });
|
|
66
|
+
throw cause;
|
|
67
|
+
}
|
|
68
|
+
let app;
|
|
69
|
+
let testingHandle;
|
|
70
|
+
try {
|
|
71
|
+
app = await internals.start({ ...defaultTestEnv, ...options.env }, {
|
|
72
|
+
database: target.connection,
|
|
73
|
+
resolvedStorage,
|
|
74
|
+
emailAdapter,
|
|
75
|
+
authResolver: sessions.resolver,
|
|
76
|
+
logger,
|
|
77
|
+
forceMemoryRealtime: true,
|
|
78
|
+
backgroundAutoStart: false,
|
|
79
|
+
captureTestingHandle: (handle) => {
|
|
80
|
+
testingHandle = handle;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
await provisionForTest(app, options.database?.schema ?? 'auto');
|
|
84
|
+
}
|
|
85
|
+
catch (cause) {
|
|
86
|
+
const cleanupErrors = [];
|
|
87
|
+
if (app) {
|
|
88
|
+
try {
|
|
89
|
+
await app.close();
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
cleanupErrors.push(error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
await target[Symbol.asyncDispose]();
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
cleanupErrors.push(error);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
await rm(storageRoot, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
cleanupErrors.push(error);
|
|
106
|
+
}
|
|
107
|
+
if (cleanupErrors.length > 0) {
|
|
108
|
+
throw new AggregateError([cause, ...cleanupErrors], '[bunderstack] test fixture setup and cleanup failed');
|
|
109
|
+
}
|
|
110
|
+
throw cause;
|
|
111
|
+
}
|
|
112
|
+
if (!testingHandle) {
|
|
113
|
+
await app.close();
|
|
114
|
+
await target[Symbol.asyncDispose]();
|
|
115
|
+
await rm(storageRoot, { recursive: true, force: true });
|
|
116
|
+
throw new Error('[bunderstack] runtime did not provide test controls');
|
|
117
|
+
}
|
|
118
|
+
const auth = createTestAuth(app, email, sessions);
|
|
119
|
+
const jobs = createTestJobs(testingHandle);
|
|
120
|
+
const deferred = [];
|
|
121
|
+
let closePromise;
|
|
122
|
+
const defer = (cleanup) => {
|
|
123
|
+
if (closePromise) {
|
|
124
|
+
throw new Error('[bunderstack] cannot defer cleanup after fixture close');
|
|
125
|
+
}
|
|
126
|
+
deferred.push(cleanup);
|
|
127
|
+
};
|
|
128
|
+
const close = () => {
|
|
129
|
+
closePromise ??= (async () => {
|
|
130
|
+
const errors = [];
|
|
131
|
+
for (const cleanup of deferred.reverse()) {
|
|
132
|
+
try {
|
|
133
|
+
await cleanup();
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
errors.push(error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
await app.close();
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
errors.push(error);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await target[Symbol.asyncDispose]();
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
errors.push(error);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await rm(storageRoot, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
errors.push(error);
|
|
156
|
+
}
|
|
157
|
+
if (errors.length === 1)
|
|
158
|
+
throw errors[0];
|
|
159
|
+
if (errors.length > 1) {
|
|
160
|
+
throw new AggregateError(errors, '[bunderstack] fixture cleanup failed');
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
163
|
+
return closePromise;
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
app,
|
|
167
|
+
auth,
|
|
168
|
+
email,
|
|
169
|
+
jobs,
|
|
170
|
+
logs,
|
|
171
|
+
storage,
|
|
172
|
+
defer,
|
|
173
|
+
client: (identity) => testClient(app, identity),
|
|
174
|
+
close,
|
|
175
|
+
[Symbol.asyncDispose]: close,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=fixture.js.map
|