neon-testing 2.0.1-beta.1 → 2.0.1-beta.12
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/dist/index.d.ts +66 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/dist/singleton.d.ts +4 -0
- package/dist/singleton.js +11 -0
- package/dist/singleton.js.map +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -0
- package/dist/vite-plugin.d.ts +2 -0
- package/dist/vite-plugin.js +17 -0
- package/dist/vite-plugin.js.map +1 -0
- package/dist/vitest-setup.d.ts +1 -0
- package/dist/vitest-setup.js +9 -0
- package/dist/vitest-setup.js.map +1 -0
- package/package.json +19 -19
- package/index.ts +0 -285
- package/singleton.ts +0 -10
- package/utils.ts +0 -2
- package/vite-plugin.ts +0 -22
- package/vitest-setup.ts +0 -12
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface NeonTestingOptions {
|
|
2
|
+
/**
|
|
3
|
+
* The Neon API key, this is used to create and teardown test branches
|
|
4
|
+
*
|
|
5
|
+
* https://neon.com/docs/manage/api-keys#creating-api-keys
|
|
6
|
+
*/
|
|
7
|
+
apiKey: string;
|
|
8
|
+
/**
|
|
9
|
+
* The Neon project ID to operate on
|
|
10
|
+
*
|
|
11
|
+
* https://console.neon.tech/app/projects
|
|
12
|
+
*/
|
|
13
|
+
projectId: string;
|
|
14
|
+
/**
|
|
15
|
+
* The parent branch ID for the new branch. If omitted or empty, the branch
|
|
16
|
+
* will be created from the project's default branch.
|
|
17
|
+
*/
|
|
18
|
+
parentBranchId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether to create a schema-only branch (default: false)
|
|
21
|
+
*/
|
|
22
|
+
schemaOnly?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The type of connection to create (pooler is recommended)
|
|
25
|
+
*/
|
|
26
|
+
endpoint?: "pooler" | "direct";
|
|
27
|
+
/**
|
|
28
|
+
* Delete the test branch in afterAll (default: true)
|
|
29
|
+
*
|
|
30
|
+
* Disabling this will leave each test branch in the Neon project after the
|
|
31
|
+
* test suite runs
|
|
32
|
+
*/
|
|
33
|
+
deleteBranch?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Automatically close Neon WebSocket connections opened during tests before
|
|
36
|
+
* deleting the branch (default: false)
|
|
37
|
+
*
|
|
38
|
+
* Suppresses the specific Neon WebSocket "Connection terminated unexpectedly"
|
|
39
|
+
* error that may surface when deleting a branch with open WebSocket
|
|
40
|
+
* connections
|
|
41
|
+
*/
|
|
42
|
+
autoCloseWebSockets?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Options for overriding test database setup (excludes apiKey) */
|
|
45
|
+
export type NeonTestingOverrides = Omit<Partial<NeonTestingOptions>, "apiKey">;
|
|
46
|
+
/**
|
|
47
|
+
* Factory function that creates a Neon test database setup/teardown function
|
|
48
|
+
* for Vitest test suites.
|
|
49
|
+
*
|
|
50
|
+
* @param apiKey - The Neon API key, this is used to create and teardown test branches
|
|
51
|
+
* @param projectId - The Neon project ID to operate on
|
|
52
|
+
* @param parentBranchId - The parent branch ID for the new branch. If omitted or empty, the branch will be created from the project's default branch.
|
|
53
|
+
* @param schemaOnly - Whether to create a schema-only branch (default: false)
|
|
54
|
+
* @param endpoint - The type of connection to create (pooler is recommended)
|
|
55
|
+
* @param deleteBranch - Delete the test branch in afterAll (default: true). Disabling this will leave each test branch in the Neon project after the test suite runs
|
|
56
|
+
* @returns A setup/teardown function for Vitest test suites
|
|
57
|
+
*
|
|
58
|
+
* Side effects:
|
|
59
|
+
* - Sets the `DATABASE_URL` environment variable to the connection URI for the
|
|
60
|
+
* new branch
|
|
61
|
+
* - Deletes the test branch after the test suite runs
|
|
62
|
+
*/
|
|
63
|
+
export declare function makeNeonTesting(factoryOptions: NeonTestingOptions): {
|
|
64
|
+
(overrides?: NeonTestingOverrides): void;
|
|
65
|
+
deleteAllTestBranches: () => Promise<void>;
|
|
66
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* https://neon.com/docs/reference/typescript-sdk
|
|
3
|
+
*/
|
|
4
|
+
import { createApiClient, EndpointType, } from "@neondatabase/api-client";
|
|
5
|
+
import { afterAll, beforeAll } from "vitest";
|
|
6
|
+
import { neonConfig } from "@neondatabase/serverless";
|
|
7
|
+
/**
|
|
8
|
+
* Creates a PostgreSQL connection URI from connection parameters
|
|
9
|
+
*
|
|
10
|
+
* @param connectionParameters - The connection parameters object
|
|
11
|
+
* @param type - The type of connection to create (pooler or direct)
|
|
12
|
+
* @returns A PostgreSQL connection URI string
|
|
13
|
+
*/
|
|
14
|
+
function createConnectionUri(connectionParameters, type) {
|
|
15
|
+
const { role, password, host, pooler_host, database } = connectionParameters.connection_parameters;
|
|
16
|
+
const hostname = type === "pooler" ? pooler_host : host;
|
|
17
|
+
return `postgresql://${role}:${password}@${hostname}/${database}?sslmode=require`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Factory function that creates a Neon test database setup/teardown function
|
|
21
|
+
* for Vitest test suites.
|
|
22
|
+
*
|
|
23
|
+
* @param apiKey - The Neon API key, this is used to create and teardown test branches
|
|
24
|
+
* @param projectId - The Neon project ID to operate on
|
|
25
|
+
* @param parentBranchId - The parent branch ID for the new branch. If omitted or empty, the branch will be created from the project's default branch.
|
|
26
|
+
* @param schemaOnly - Whether to create a schema-only branch (default: false)
|
|
27
|
+
* @param endpoint - The type of connection to create (pooler is recommended)
|
|
28
|
+
* @param deleteBranch - Delete the test branch in afterAll (default: true). Disabling this will leave each test branch in the Neon project after the test suite runs
|
|
29
|
+
* @returns A setup/teardown function for Vitest test suites
|
|
30
|
+
*
|
|
31
|
+
* Side effects:
|
|
32
|
+
* - Sets the `DATABASE_URL` environment variable to the connection URI for the
|
|
33
|
+
* new branch
|
|
34
|
+
* - Deletes the test branch after the test suite runs
|
|
35
|
+
*/
|
|
36
|
+
export function makeNeonTesting(factoryOptions) {
|
|
37
|
+
const apiClient = createApiClient({ apiKey: factoryOptions.apiKey });
|
|
38
|
+
/**
|
|
39
|
+
* Delete all test branches
|
|
40
|
+
*/
|
|
41
|
+
async function deleteAllTestBranches() {
|
|
42
|
+
const { data } = await apiClient.listProjectBranches({
|
|
43
|
+
projectId: factoryOptions.projectId,
|
|
44
|
+
});
|
|
45
|
+
for (const branch of data.branches) {
|
|
46
|
+
const isTestBranch = data.annotations[branch.id]?.value["integration-test"] === "true";
|
|
47
|
+
if (isTestBranch) {
|
|
48
|
+
await apiClient.deleteProjectBranch(factoryOptions.projectId, branch.id);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const testDbSetup = (
|
|
53
|
+
/** Override any factory options except apiKey */
|
|
54
|
+
overrides) => {
|
|
55
|
+
// Merge factory options with overrides
|
|
56
|
+
const options = { ...factoryOptions, ...overrides };
|
|
57
|
+
// Each test file gets its own branch ID and database client
|
|
58
|
+
let branchId;
|
|
59
|
+
// List of tracked Neon WebSocket connections
|
|
60
|
+
const neonSockets = new Set();
|
|
61
|
+
// Custom WebSocket constructor that tracks Neon WebSocket connections
|
|
62
|
+
class TrackingWebSocket extends WebSocket {
|
|
63
|
+
constructor(url) {
|
|
64
|
+
super(url);
|
|
65
|
+
// Only track Neon WebSocket connections
|
|
66
|
+
if (!url.includes(".neon.tech/"))
|
|
67
|
+
return;
|
|
68
|
+
neonSockets.add(this);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a new test branch
|
|
73
|
+
*
|
|
74
|
+
* @returns The connection URI for the new branch
|
|
75
|
+
*/
|
|
76
|
+
async function createBranch() {
|
|
77
|
+
const { data } = await apiClient.createProjectBranch(options.projectId, {
|
|
78
|
+
branch: {
|
|
79
|
+
name: `test/${crypto.randomUUID()}`,
|
|
80
|
+
parent_id: options.parentBranchId,
|
|
81
|
+
init_source: options.schemaOnly ? "schema-only" : undefined,
|
|
82
|
+
},
|
|
83
|
+
endpoints: [{ type: EndpointType.ReadWrite }],
|
|
84
|
+
annotation_value: {
|
|
85
|
+
"integration-test": "true",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
branchId = data.branch.id;
|
|
89
|
+
const [connectionUri] = data.connection_uris ?? [];
|
|
90
|
+
if (!connectionUri) {
|
|
91
|
+
throw new Error("No connection URI found");
|
|
92
|
+
}
|
|
93
|
+
return createConnectionUri(connectionUri, options.endpoint ?? "pooler");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Delete the test branch
|
|
97
|
+
*/
|
|
98
|
+
async function deleteBranch() {
|
|
99
|
+
if (!branchId) {
|
|
100
|
+
throw new Error("No branch to delete");
|
|
101
|
+
}
|
|
102
|
+
await apiClient.deleteProjectBranch(options.projectId, branchId);
|
|
103
|
+
branchId = undefined;
|
|
104
|
+
}
|
|
105
|
+
beforeAll(async () => {
|
|
106
|
+
process.env.DATABASE_URL = await withRetry(createBranch, {
|
|
107
|
+
maxRetries: 5,
|
|
108
|
+
baseDelayMs: 1000,
|
|
109
|
+
});
|
|
110
|
+
if (options.autoCloseWebSockets) {
|
|
111
|
+
// Install a custom WebSocket constructor that tracks Neon WebSocket
|
|
112
|
+
// connections and closes them before deleting the branch
|
|
113
|
+
neonConfig.webSocketConstructor = TrackingWebSocket;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
afterAll(async () => {
|
|
117
|
+
process.env.DATABASE_URL = undefined;
|
|
118
|
+
// Close all tracked Neon WebSocket connections before deleting the branch
|
|
119
|
+
if (options.autoCloseWebSockets) {
|
|
120
|
+
// Suppress Neon WebSocket "Connection terminated unexpectedly" error
|
|
121
|
+
process.prependListener("uncaughtException", neonWsErrorHandler);
|
|
122
|
+
// Close tracked Neon WebSocket connections before deleting the branch
|
|
123
|
+
neonSockets.forEach((ws) => ws.close());
|
|
124
|
+
}
|
|
125
|
+
if (options.deleteBranch !== false) {
|
|
126
|
+
await deleteBranch();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
// Attach the utility
|
|
131
|
+
testDbSetup.deleteAllTestBranches = deleteAllTestBranches;
|
|
132
|
+
return testDbSetup;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Error handler: Suppress Neon WebSocket "Connection terminated unexpectedly"
|
|
136
|
+
* error
|
|
137
|
+
*/
|
|
138
|
+
const neonWsErrorHandler = (error) => {
|
|
139
|
+
const isNeonWsClose = error.message.includes("Connection terminated unexpectedly") &&
|
|
140
|
+
error.stack?.includes("@neondatabase/serverless");
|
|
141
|
+
if (isNeonWsClose) {
|
|
142
|
+
// Swallow this specific Neon WS termination error
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// For any other error, detach and rethrow
|
|
146
|
+
throw error;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Reusable API call wrapper with automatic retry on 423 errors with exponential
|
|
150
|
+
* backoff
|
|
151
|
+
*
|
|
152
|
+
* https://neon.com/docs/reference/typescript-sdk#error-handling
|
|
153
|
+
* https://neon.com/docs/changelog/2022-07-20
|
|
154
|
+
*/
|
|
155
|
+
async function withRetry(fn, options) {
|
|
156
|
+
if (!Number.isInteger(options.maxRetries) || options.maxRetries <= 0) {
|
|
157
|
+
throw new Error("maxRetries must be a positive integer");
|
|
158
|
+
}
|
|
159
|
+
if (!Number.isInteger(options.baseDelayMs) || options.baseDelayMs <= 0) {
|
|
160
|
+
throw new Error("baseDelayMs must be a positive integer");
|
|
161
|
+
}
|
|
162
|
+
for (let attempt = 1; attempt <= options.maxRetries; attempt++) {
|
|
163
|
+
try {
|
|
164
|
+
return await fn();
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
const status = error?.response?.status;
|
|
168
|
+
if (status === 423 && attempt < options.maxRetries) {
|
|
169
|
+
const delay = options.baseDelayMs * Math.pow(2, attempt - 1);
|
|
170
|
+
console.log(`API call failed with 423, retrying in ${delay}ms (attempt ${attempt}/${options.maxRetries})`);
|
|
171
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
throw new Error("apiCallWithRetry reached unexpected end");
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton.js","sourceRoot":"","sources":["../singleton.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,aAAa,CAAI,OAAgB;IAC/C,IAAI,QAAuB,CAAC;IAC5B,OAAO,GAAG,EAAE;QACV,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
export function neonTesting() {
|
|
3
|
+
return {
|
|
4
|
+
name: "neon-testing-plugin",
|
|
5
|
+
enforce: "pre",
|
|
6
|
+
config(user) {
|
|
7
|
+
const setupPath = fileURLToPath(new URL("./vitest-setup.ts", import.meta.url));
|
|
8
|
+
const setup = new Set([...(user.test?.setupFiles ?? []), setupPath]);
|
|
9
|
+
return {
|
|
10
|
+
test: {
|
|
11
|
+
setupFiles: Array.from(setup),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=vite-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin.js","sourceRoot":"","sources":["../vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,KAAK;QACd,MAAM,CAAC,IAAI;YACT,MAAM,SAAS,GAAG,aAAa,CAC7B,IAAI,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAC9C,CAAC;YAEF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;YAErE,OAAO;gBACL,IAAI,EAAE;oBACJ,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;iBAC9B;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare const isVitest: string | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const isVitest = process.env.VITEST;
|
|
3
|
+
if (isVitest) {
|
|
4
|
+
if (process.env.DATABASE_URL) {
|
|
5
|
+
console.warn("[neon-testing] Clearing existing DATABASE_URL in test environment");
|
|
6
|
+
}
|
|
7
|
+
delete process.env.DATABASE_URL;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=vitest-setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest-setup.js","sourceRoot":"","sources":["../vitest-setup.ts"],"names":[],"mappings":";AAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;AAEpC,IAAI,QAAQ,EAAE,CAAC;IACb,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,CACV,mEAAmE,CACpE,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AAClC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon-testing",
|
|
3
|
-
"version": "2.0.1-beta.
|
|
3
|
+
"version": "2.0.1-beta.12",
|
|
4
4
|
"description": "A Vitest utility for seamless integration tests with Neon Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -16,32 +16,32 @@
|
|
|
16
16
|
"type": "module",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": {
|
|
19
|
-
"types": "./index.ts",
|
|
20
|
-
"import": "./index.
|
|
21
|
-
"default": "./index.ts"
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
22
21
|
},
|
|
23
22
|
"./utils": {
|
|
24
|
-
"types": "./utils.ts",
|
|
25
|
-
"import": "./utils.
|
|
26
|
-
|
|
23
|
+
"types": "./dist/utils.d.ts",
|
|
24
|
+
"import": "./dist/utils.js"
|
|
25
|
+
},
|
|
26
|
+
"./vite-plugin": {
|
|
27
|
+
"types": "./dist/vite-plugin.d.ts",
|
|
28
|
+
"import": "./dist/vite-plugin.js"
|
|
27
29
|
}
|
|
28
30
|
},
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
29
32
|
"files": [
|
|
30
|
-
"
|
|
31
|
-
"utils.ts",
|
|
32
|
-
"singleton.ts",
|
|
33
|
-
"vite-plugin.ts",
|
|
34
|
-
"vitest-setup.ts"
|
|
33
|
+
"dist"
|
|
35
34
|
],
|
|
36
35
|
"scripts": {
|
|
37
|
-
"test": "vitest",
|
|
36
|
+
"test": "bun run build && vitest run",
|
|
38
37
|
"format": "prettier --write .",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"release:
|
|
42
|
-
"release:
|
|
43
|
-
"
|
|
44
|
-
"
|
|
38
|
+
"build": "rm -rf dist && tsc",
|
|
39
|
+
"preversion": "bun run build && vitest run && prettier --check .",
|
|
40
|
+
"release:patch": "bun run preversion && bun pm version patch && bun publish --tag latest",
|
|
41
|
+
"release:minor": "bun run preversion && bun pm version minor && bun publish --tag latest",
|
|
42
|
+
"release:major": "bun run preversion && bun pm version major && bun publish --tag latest",
|
|
43
|
+
"release:beta": "bun run preversion && bun pm version prerelease --preid=beta && bun publish --tag beta",
|
|
44
|
+
"postpublish": "git push --follow-tags"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@neondatabase/api-client": "^2.2.0"
|
package/index.ts
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* https://neon.com/docs/reference/typescript-sdk
|
|
3
|
-
*/
|
|
4
|
-
import {
|
|
5
|
-
createApiClient,
|
|
6
|
-
EndpointType,
|
|
7
|
-
type ConnectionDetails,
|
|
8
|
-
} from "@neondatabase/api-client";
|
|
9
|
-
import { afterAll, beforeAll } from "vitest";
|
|
10
|
-
import { neonConfig } from "@neondatabase/serverless";
|
|
11
|
-
|
|
12
|
-
export * from "./utils";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Creates a PostgreSQL connection URI from connection parameters
|
|
16
|
-
*
|
|
17
|
-
* @param connectionParameters - The connection parameters object
|
|
18
|
-
* @param type - The type of connection to create (pooler or direct)
|
|
19
|
-
* @returns A PostgreSQL connection URI string
|
|
20
|
-
*/
|
|
21
|
-
function createConnectionUri(
|
|
22
|
-
connectionParameters: ConnectionDetails,
|
|
23
|
-
type: "pooler" | "direct",
|
|
24
|
-
) {
|
|
25
|
-
const { role, password, host, pooler_host, database } =
|
|
26
|
-
connectionParameters.connection_parameters;
|
|
27
|
-
|
|
28
|
-
const hostname = type === "pooler" ? pooler_host : host;
|
|
29
|
-
|
|
30
|
-
return `postgresql://${role}:${password}@${hostname}/${database}?sslmode=require`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface NeonTestingOptions {
|
|
34
|
-
/**
|
|
35
|
-
* The Neon API key, this is used to create and teardown test branches
|
|
36
|
-
*
|
|
37
|
-
* https://neon.com/docs/manage/api-keys#creating-api-keys
|
|
38
|
-
*/
|
|
39
|
-
apiKey: string;
|
|
40
|
-
/**
|
|
41
|
-
* The Neon project ID to operate on
|
|
42
|
-
*
|
|
43
|
-
* https://console.neon.tech/app/projects
|
|
44
|
-
*/
|
|
45
|
-
projectId: string;
|
|
46
|
-
/**
|
|
47
|
-
* The parent branch ID for the new branch. If omitted or empty, the branch
|
|
48
|
-
* will be created from the project's default branch.
|
|
49
|
-
*/
|
|
50
|
-
parentBranchId?: string;
|
|
51
|
-
/**
|
|
52
|
-
* Whether to create a schema-only branch (default: false)
|
|
53
|
-
*/
|
|
54
|
-
schemaOnly?: boolean;
|
|
55
|
-
/**
|
|
56
|
-
* The type of connection to create (pooler is recommended)
|
|
57
|
-
*/
|
|
58
|
-
endpoint?: "pooler" | "direct";
|
|
59
|
-
/**
|
|
60
|
-
* Delete the test branch in afterAll (default: true)
|
|
61
|
-
*
|
|
62
|
-
* Disabling this will leave each test branch in the Neon project after the
|
|
63
|
-
* test suite runs
|
|
64
|
-
*/
|
|
65
|
-
deleteBranch?: boolean;
|
|
66
|
-
/**
|
|
67
|
-
* Automatically close Neon WebSocket connections opened during tests before
|
|
68
|
-
* deleting the branch (default: false)
|
|
69
|
-
*
|
|
70
|
-
* Suppresses the specific Neon WebSocket "Connection terminated unexpectedly"
|
|
71
|
-
* error that may surface when deleting a branch with open WebSocket
|
|
72
|
-
* connections
|
|
73
|
-
*/
|
|
74
|
-
autoCloseWebSockets?: boolean;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Options for overriding test database setup (excludes apiKey) */
|
|
78
|
-
export type NeonTestingOverrides = Omit<Partial<NeonTestingOptions>, "apiKey">;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Factory function that creates a Neon test database setup/teardown function
|
|
82
|
-
* for Vitest test suites.
|
|
83
|
-
*
|
|
84
|
-
* @param apiKey - The Neon API key, this is used to create and teardown test branches
|
|
85
|
-
* @param projectId - The Neon project ID to operate on
|
|
86
|
-
* @param parentBranchId - The parent branch ID for the new branch. If omitted or empty, the branch will be created from the project's default branch.
|
|
87
|
-
* @param schemaOnly - Whether to create a schema-only branch (default: false)
|
|
88
|
-
* @param endpoint - The type of connection to create (pooler is recommended)
|
|
89
|
-
* @param deleteBranch - Delete the test branch in afterAll (default: true). Disabling this will leave each test branch in the Neon project after the test suite runs
|
|
90
|
-
* @returns A setup/teardown function for Vitest test suites
|
|
91
|
-
*
|
|
92
|
-
* Side effects:
|
|
93
|
-
* - Sets the `DATABASE_URL` environment variable to the connection URI for the
|
|
94
|
-
* new branch
|
|
95
|
-
* - Deletes the test branch after the test suite runs
|
|
96
|
-
*/
|
|
97
|
-
export function makeNeonTesting(factoryOptions: NeonTestingOptions) {
|
|
98
|
-
const apiClient = createApiClient({ apiKey: factoryOptions.apiKey });
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Delete all test branches
|
|
102
|
-
*/
|
|
103
|
-
async function deleteAllTestBranches() {
|
|
104
|
-
const { data } = await apiClient.listProjectBranches({
|
|
105
|
-
projectId: factoryOptions.projectId,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
for (const branch of data.branches) {
|
|
109
|
-
const isTestBranch =
|
|
110
|
-
data.annotations[branch.id]?.value["integration-test"] === "true";
|
|
111
|
-
|
|
112
|
-
if (isTestBranch) {
|
|
113
|
-
await apiClient.deleteProjectBranch(
|
|
114
|
-
factoryOptions.projectId,
|
|
115
|
-
branch.id,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const testDbSetup = (
|
|
122
|
-
/** Override any factory options except apiKey */
|
|
123
|
-
overrides?: NeonTestingOverrides,
|
|
124
|
-
) => {
|
|
125
|
-
// Merge factory options with overrides
|
|
126
|
-
const options = { ...factoryOptions, ...overrides };
|
|
127
|
-
|
|
128
|
-
// Each test file gets its own branch ID and database client
|
|
129
|
-
let branchId: string | undefined;
|
|
130
|
-
|
|
131
|
-
// List of tracked Neon WebSocket connections
|
|
132
|
-
const neonSockets = new Set<WebSocket>();
|
|
133
|
-
|
|
134
|
-
// Custom WebSocket constructor that tracks Neon WebSocket connections
|
|
135
|
-
class TrackingWebSocket extends WebSocket {
|
|
136
|
-
constructor(url: string) {
|
|
137
|
-
super(url);
|
|
138
|
-
|
|
139
|
-
// Only track Neon WebSocket connections
|
|
140
|
-
if (!url.includes(".neon.tech/")) return;
|
|
141
|
-
|
|
142
|
-
neonSockets.add(this);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Create a new test branch
|
|
148
|
-
*
|
|
149
|
-
* @returns The connection URI for the new branch
|
|
150
|
-
*/
|
|
151
|
-
async function createBranch() {
|
|
152
|
-
const { data } = await apiClient.createProjectBranch(options.projectId, {
|
|
153
|
-
branch: {
|
|
154
|
-
name: `test/${crypto.randomUUID()}`,
|
|
155
|
-
parent_id: options.parentBranchId,
|
|
156
|
-
init_source: options.schemaOnly ? "schema-only" : undefined,
|
|
157
|
-
},
|
|
158
|
-
endpoints: [{ type: EndpointType.ReadWrite }],
|
|
159
|
-
annotation_value: {
|
|
160
|
-
"integration-test": "true",
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
branchId = data.branch.id;
|
|
165
|
-
|
|
166
|
-
const [connectionUri] = data.connection_uris ?? [];
|
|
167
|
-
|
|
168
|
-
if (!connectionUri) {
|
|
169
|
-
throw new Error("No connection URI found");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return createConnectionUri(connectionUri, options.endpoint ?? "pooler");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Delete the test branch
|
|
177
|
-
*/
|
|
178
|
-
async function deleteBranch() {
|
|
179
|
-
if (!branchId) {
|
|
180
|
-
throw new Error("No branch to delete");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
await apiClient.deleteProjectBranch(options.projectId, branchId);
|
|
184
|
-
branchId = undefined;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
beforeAll(async () => {
|
|
188
|
-
process.env.DATABASE_URL = await withRetry(createBranch, {
|
|
189
|
-
maxRetries: 5,
|
|
190
|
-
baseDelayMs: 1000,
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
if (options.autoCloseWebSockets) {
|
|
194
|
-
// Install a custom WebSocket constructor that tracks Neon WebSocket
|
|
195
|
-
// connections and closes them before deleting the branch
|
|
196
|
-
neonConfig.webSocketConstructor = TrackingWebSocket;
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
afterAll(async () => {
|
|
201
|
-
process.env.DATABASE_URL = undefined;
|
|
202
|
-
|
|
203
|
-
// Close all tracked Neon WebSocket connections before deleting the branch
|
|
204
|
-
if (options.autoCloseWebSockets) {
|
|
205
|
-
// Suppress Neon WebSocket "Connection terminated unexpectedly" error
|
|
206
|
-
process.prependListener("uncaughtException", neonWsErrorHandler);
|
|
207
|
-
|
|
208
|
-
// Close tracked Neon WebSocket connections before deleting the branch
|
|
209
|
-
neonSockets.forEach((ws) => ws.close());
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (options.deleteBranch !== false) {
|
|
213
|
-
await deleteBranch();
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// Attach the utility
|
|
219
|
-
testDbSetup.deleteAllTestBranches = deleteAllTestBranches;
|
|
220
|
-
|
|
221
|
-
return testDbSetup;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Error handler: Suppress Neon WebSocket "Connection terminated unexpectedly"
|
|
226
|
-
* error
|
|
227
|
-
*/
|
|
228
|
-
const neonWsErrorHandler = (error: Error) => {
|
|
229
|
-
const isNeonWsClose =
|
|
230
|
-
error.message.includes("Connection terminated unexpectedly") &&
|
|
231
|
-
error.stack?.includes("@neondatabase/serverless");
|
|
232
|
-
|
|
233
|
-
if (isNeonWsClose) {
|
|
234
|
-
// Swallow this specific Neon WS termination error
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// For any other error, detach and rethrow
|
|
239
|
-
throw error;
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Reusable API call wrapper with automatic retry on 423 errors with exponential
|
|
244
|
-
* backoff
|
|
245
|
-
*
|
|
246
|
-
* https://neon.com/docs/reference/typescript-sdk#error-handling
|
|
247
|
-
* https://neon.com/docs/changelog/2022-07-20
|
|
248
|
-
*/
|
|
249
|
-
async function withRetry<T>(
|
|
250
|
-
fn: () => Promise<T>,
|
|
251
|
-
options: {
|
|
252
|
-
maxRetries: number;
|
|
253
|
-
baseDelayMs: number;
|
|
254
|
-
},
|
|
255
|
-
): Promise<T> {
|
|
256
|
-
if (!Number.isInteger(options.maxRetries) || options.maxRetries <= 0) {
|
|
257
|
-
throw new Error("maxRetries must be a positive integer");
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (!Number.isInteger(options.baseDelayMs) || options.baseDelayMs <= 0) {
|
|
261
|
-
throw new Error("baseDelayMs must be a positive integer");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
for (let attempt = 1; attempt <= options.maxRetries; attempt++) {
|
|
265
|
-
try {
|
|
266
|
-
return await fn();
|
|
267
|
-
} catch (error: any) {
|
|
268
|
-
const status = error?.response?.status;
|
|
269
|
-
|
|
270
|
-
if (status === 423 && attempt < options.maxRetries) {
|
|
271
|
-
const delay = options.baseDelayMs * Math.pow(2, attempt - 1);
|
|
272
|
-
|
|
273
|
-
console.log(
|
|
274
|
-
`API call failed with 423, retrying in ${delay}ms (attempt ${attempt}/${options.maxRetries})`,
|
|
275
|
-
);
|
|
276
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
277
|
-
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
throw error;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
throw new Error("apiCallWithRetry reached unexpected end");
|
|
285
|
-
}
|
package/singleton.ts
DELETED
package/utils.ts
DELETED
package/vite-plugin.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
2
|
-
import type { Plugin, UserConfig } from "vite";
|
|
3
|
-
|
|
4
|
-
export function neonTesting() {
|
|
5
|
-
return {
|
|
6
|
-
name: "neon-testing-plugin",
|
|
7
|
-
enforce: "pre",
|
|
8
|
-
config(user: any) {
|
|
9
|
-
const setupPath = fileURLToPath(
|
|
10
|
-
new URL("./vitest-setup.ts", import.meta.url),
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
const setup = new Set([...(user.test?.setupFiles ?? []), setupPath]);
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
test: {
|
|
17
|
-
setupFiles: Array.from(setup),
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
},
|
|
21
|
-
} satisfies Plugin;
|
|
22
|
-
}
|
package/vitest-setup.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
const isVitest =
|
|
2
|
-
process.env.VITEST === "true" || !!process.env.VITEST_WORKER_ID;
|
|
3
|
-
|
|
4
|
-
if (isVitest) {
|
|
5
|
-
if (process.env.DATABASE_URL) {
|
|
6
|
-
console.warn(
|
|
7
|
-
"[neon-testing] Clearing existing DATABASE_URL in test environment",
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
delete process.env.DATABASE_URL;
|
|
12
|
-
}
|