Skip to content

Commit dc8b966

Browse files
committed
Added Vercel Edge and Node runtime tests
1 parent e17e0f2 commit dc8b966

File tree

8 files changed

+211
-12
lines changed

8 files changed

+211
-12
lines changed

.github/workflows/test.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
# goal
2727
- name: Run tests in Node 16 (no native WebSocket or fetch), node environment
28-
run: npm run testNode
28+
run: npm run test:node
2929

3030
test-node-lts:
3131
runs-on: ubuntu-latest
@@ -41,13 +41,16 @@ jobs:
4141

4242
# goal
4343
- name: Run tests in Node LTS, node environment
44-
run: npm run testNode
44+
run: npm run test:node
4545

4646
- name: Run tests in Node LTS, edge-runtime environment
47-
run: npm run testEdge
47+
run: npm run test:edge
4848

49-
- name: Run Chromium tests
50-
run: npm run testChromium
49+
- name: Run browser tests
50+
run: npm run test:browser
5151

5252
- name: Run Cloudflare Workers tests
53-
run: npm run testCfWorker
53+
run: npm run test:cloudflare
54+
55+
- name: Run Vercel Functions tests
56+
run: npm run test:vercel

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@
4646
"cfDeploy": "npm run build && wrangler deploy",
4747
"format": "prettier -c .",
4848
"format:fix": "prettier -w .",
49-
"testNode": "./export.sh && vitest run --dir=tests/cli --environment=node --no-file-parallelism --typecheck",
50-
"testEdge": "./export.sh && vitest run --dir=tests/cli --environment=edge-runtime --no-file-parallelism",
51-
"testChromium": "./export.sh && playwright install --with-deps chromium && vitest run --dir=tests/browser --browser --config=tests/browser/vite.config.mts --no-file-parallelism",
52-
"testCfWorker": "./export.sh && vitest run --dir=tests/cloudflareWorker --no-file-parallelism"
49+
"test:node": "./export.sh && vitest run --dir=tests/cli --environment=node --no-file-parallelism --typecheck",
50+
"test:edge": "./export.sh && vitest run --dir=tests/cli --environment=edge-runtime --no-file-parallelism",
51+
"test:cloudflare": "./export.sh && vitest run --dir=tests/cloudflare --no-file-parallelism",
52+
"test:vercel": "./export.sh && vitest run --dir=tests/vercel --no-file-parallelism",
53+
"test:browser": "./export.sh && playwright install --with-deps firefox && vitest run --dir=tests/browser --browser --config=tests/browser/vite.config.mts --no-file-parallelism"
5354
},
5455
"devDependencies": {
5556
"@cloudflare/workers-types": "^4.20241230.0",
@@ -58,6 +59,8 @@
5859
"@types/events": "^3.0.0",
5960
"@types/pg": "^8.6.5",
6061
"@types/ws": "^8.5.4",
62+
"@vercel/edge": "^1.2.1",
63+
"@vercel/sdk": "^1.2.2",
6164
"@vitest/browser": "^2.1.8",
6265
"dotenv": "^16.0.3",
6366
"esbuild": "^0.24.0",

tests/browser/vite.config.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { defineConfig } from 'vite';
33
export default defineConfig({
44
test: {
55
browser: {
6-
name: 'chromium',
6+
name: 'firefox',
77
provider: 'playwright',
88
headless: true,
99
screenshotFailures: false,
File renamed without changes.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default {
3131
ctx.waitUntil(pool.end());
3232

3333
// test http fetch
34-
q = "SELECT 'fetch' AS fetchStr";
34+
q = "SELECT 'fetch' AS fetchstr";
3535
const sql = neon(DATABASE_URL, { fullResults: true });
3636
const { rows: fetchRows } = await sql(q);
3737

tests/vercel/function.mjs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// at deployment we copy this from index.mjs in the npm package
2+
import { Client, Pool, neon } from '../../neondatabase-serverless.mjs';
3+
4+
// note: we use this file for both Edge and Node runtimes
5+
// - for both, we append a definition for the variable `moment`
6+
// - for Edge, we also append/export the appropriate `config` variable
7+
8+
export default async (req, rc) => {
9+
// rc is response for nodejs, context for edge
10+
11+
const DATABASE_URL = process.env.DATABASE_URL; // note: destructuring may not work as expected here
12+
let q;
13+
14+
// test WebSockets (Client.prototype.query)
15+
q = "SELECT 'client' AS clientstr";
16+
const client = new Client(DATABASE_URL);
17+
await client.connect();
18+
const { rows: clientRows } = await client.query(q);
19+
await client.end(); // await is OK here, but waitUntil(...) is quicker for real use-cases
20+
21+
// test WebSockets (Pool.prototype.query)
22+
q = "SELECT 'pool' AS poolstr";
23+
const pool = new Pool({ connectionString: DATABASE_URL });
24+
const { rows: poolRows } = await pool.query(q);
25+
26+
// test WebSockets (Pool.prototype.connect)
27+
q = "SELECT 'poolclient' AS poolclientstr";
28+
const poolClient = await pool.connect();
29+
const { rows: poolClientRows } = await poolClient.query(q);
30+
poolClient.release();
31+
await pool.end();
32+
33+
// test http fetch
34+
q = "SELECT 'fetch' AS fetchstr";
35+
const sql = neon(DATABASE_URL, { fullResults: true });
36+
const { rows: fetchRows } = await sql(q);
37+
38+
// respond
39+
const responseData = {
40+
clientRows,
41+
poolRows,
42+
poolClientRows,
43+
fetchRows,
44+
moment,
45+
};
46+
47+
if (rc.json) {
48+
// nodejs runtime
49+
rc.json(responseData);
50+
} else {
51+
// edge runtime
52+
return new Response(JSON.stringify(responseData), {
53+
headers: { 'Content-Type': 'application/json' },
54+
});
55+
}
56+
};

tests/vercel/vercel.test.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { expect, test, beforeAll, afterAll } from 'vitest';
2+
import fs from 'fs/promises';
3+
import { Vercel } from '@vercel/sdk';
4+
5+
const projectName = 'neon-serverless-tests-project';
6+
const vercel = new Vercel({
7+
bearerToken: process.env.VITE_VERCEL_TOKEN,
8+
});
9+
10+
test(
11+
'Vercel functions (Edge and Node) should respond with queried values',
12+
{ timeout: 90000 }, // 90-sec timeout since deploying to Vercel can take time
13+
async () => {
14+
// create project
15+
try {
16+
await vercel.projects.createProject({
17+
requestBody: { name: projectName },
18+
});
19+
} catch (e) {
20+
// http status 409 Conflict just means we created the project before, which is fine
21+
if (e.statusCode !== 409) throw e;
22+
}
23+
24+
// update project to remove signed-in requirement for production deploy links
25+
await vercel.projects.updateProject({
26+
idOrName: projectName,
27+
requestBody: {
28+
ssoProtection: { deploymentType: 'preview' }, // i.e. NOT production
29+
},
30+
});
31+
32+
// set environment variable(s)
33+
await vercel.projects.createProjectEnv({
34+
idOrName: projectName,
35+
upsert: 'true',
36+
requestBody: [
37+
{
38+
key: 'DATABASE_URL',
39+
value: process.env.VITE_NEON_DB_POOLER_URL!,
40+
type: 'encrypted',
41+
target: ['production'],
42+
},
43+
],
44+
});
45+
46+
// deploy
47+
const utf8 = { encoding: 'utf-8' } as const;
48+
const moment = Date.now(); // used for double-checking we're fetching from this new deployment
49+
50+
const packageSrc = await fs.readFile('dist/npm/index.mjs', utf8);
51+
const fnRawSrc = await fs.readFile('tests/vercel/function.mjs', utf8);
52+
const fnSrc = fnRawSrc + `\nconst moment = ${moment};`;
53+
54+
const edgeSrc = fnSrc + "\nexport const config = { runtime: 'edge' };";
55+
const nodeSrc = fnSrc; // specifying `runtime: "nodejs"` causes an error
56+
57+
const createDeploymentResponse = await vercel.deployments.createDeployment({
58+
requestBody: {
59+
project: projectName,
60+
target: 'production',
61+
name: 'neon-serverless-tests-deployment',
62+
projectSettings: {
63+
rootDirectory: 'public',
64+
sourceFilesOutsideRootDirectory: true,
65+
},
66+
files: [
67+
{
68+
file: 'public/api/edge.mjs',
69+
data: edgeSrc,
70+
...utf8,
71+
},
72+
{
73+
file: 'public/api/node.mjs',
74+
data: nodeSrc,
75+
...utf8,
76+
},
77+
{
78+
file: 'neondatabase-serverless.mjs',
79+
data: packageSrc,
80+
...utf8,
81+
},
82+
],
83+
},
84+
});
85+
86+
const deploymentId = createDeploymentResponse.id;
87+
88+
// await deployment
89+
let status, host;
90+
do {
91+
await new Promise((resolve) => setTimeout(resolve, 2000)); // wait 2 seconds between checks
92+
const statusResponse = await vercel.deployments.getDeployment({
93+
idOrUrl: deploymentId,
94+
});
95+
status = statusResponse.status;
96+
host = statusResponse.url;
97+
} while (status === 'BUILDING' || status === 'INITIALIZING');
98+
99+
if (status !== 'READY') {
100+
throw new Error(`Unexpected deployment status: ${status}`);
101+
}
102+
103+
console.log(`Vercel deployment: ${host}`);
104+
105+
// fetches
106+
for (const runtime of ['edge', 'node']) {
107+
const url = `https://${host}/api/${runtime}`;
108+
const fetchResponse = await fetch(url);
109+
const fetchJson = await fetchResponse.json();
110+
expect(fetchJson).toStrictEqual({
111+
clientRows: [{ clientstr: 'client' }],
112+
poolRows: [{ poolstr: 'pool' }],
113+
poolClientRows: [{ poolclientstr: 'poolclient' }],
114+
fetchRows: [{ fetchstr: 'fetch' }],
115+
moment, // a definition for this variable is appended on deploy
116+
});
117+
}
118+
},
119+
);

0 commit comments

Comments
 (0)