Skip to content

Commit 0c1f8e0

Browse files
chargomeclaude
andcommitted
test(e2e): Add node-prisma-8 test app
Express app on Prisma 8 rc.8 with Postgres 16 in Docker, asserting the `prisma:client:operation` spans and the `pg` spans nested under them through the runtime hook. Refs #24237 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 685f3c5 commit 0c1f8e0

15 files changed

Lines changed: 824 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
services:
2+
db:
3+
# Prisma 8 requires PostgreSQL 15 or newer.
4+
image: postgres:16
5+
restart: always
6+
container_name: e2e-tests-node-prisma-8
7+
ports:
8+
- '5438:5432'
9+
environment:
10+
POSTGRES_USER: prisma
11+
POSTGRES_PASSWORD: prisma
12+
POSTGRES_DB: tests
13+
# Dumped from `prisma-cli db init`; the Prisma 8 CLI needs Node 22.18+, so it isn't run at test time.
14+
volumes:
15+
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
16+
healthcheck:
17+
test: ['CMD-SHELL', 'pg_isready -U prisma -d tests']
18+
interval: 2s
19+
timeout: 3s
20+
retries: 30
21+
start_period: 5s
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { execSync } from 'child_process';
2+
import { dirname } from 'path';
3+
import { fileURLToPath } from 'url';
4+
5+
const __dirname = dirname(fileURLToPath(import.meta.url));
6+
7+
export default async function globalSetup() {
8+
// Start PostgreSQL via Docker Compose. `--wait` blocks until the healthcheck
9+
// in docker-compose.yml passes, so the app can connect immediately.
10+
execSync('docker compose up -d --wait', {
11+
cwd: __dirname,
12+
stdio: 'inherit',
13+
});
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { execSync } from 'child_process';
2+
import { dirname } from 'path';
3+
import { fileURLToPath } from 'url';
4+
5+
const __dirname = dirname(fileURLToPath(import.meta.url));
6+
7+
export default async function globalTeardown() {
8+
execSync('docker compose down --volumes', {
9+
cwd: __dirname,
10+
stdio: 'inherit',
11+
});
12+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
CREATE SCHEMA prisma_contract;
2+
CREATE TABLE prisma_contract.contract (
3+
core_hash text NOT NULL,
4+
created_at timestamp with time zone DEFAULT now() NOT NULL,
5+
contract_json jsonb NOT NULL
6+
);
7+
CREATE TABLE prisma_contract.ledger (
8+
id bigint NOT NULL,
9+
created_at timestamp with time zone DEFAULT now() NOT NULL,
10+
space text NOT NULL,
11+
migration_name text NOT NULL,
12+
migration_hash text NOT NULL,
13+
origin_core_hash text,
14+
origin_profile_hash text,
15+
destination_core_hash text NOT NULL,
16+
destination_profile_hash text,
17+
operations jsonb NOT NULL
18+
);
19+
CREATE SEQUENCE prisma_contract.ledger_id_seq
20+
START WITH 1
21+
INCREMENT BY 1
22+
NO MINVALUE
23+
NO MAXVALUE
24+
CACHE 1;
25+
ALTER SEQUENCE prisma_contract.ledger_id_seq OWNED BY prisma_contract.ledger.id;
26+
CREATE TABLE prisma_contract.marker (
27+
space text DEFAULT 'app'::text NOT NULL,
28+
core_hash text NOT NULL,
29+
profile_hash text NOT NULL,
30+
contract_json jsonb,
31+
canonical_version integer,
32+
updated_at timestamp with time zone DEFAULT now() NOT NULL,
33+
app_tag text,
34+
meta jsonb DEFAULT '{}'::jsonb NOT NULL,
35+
invariants text[] DEFAULT '{}'::text[] NOT NULL
36+
);
37+
CREATE TABLE public."user" (
38+
email text NOT NULL,
39+
id integer NOT NULL,
40+
name text
41+
);
42+
CREATE SEQUENCE public.user_id_seq
43+
AS integer
44+
START WITH 1
45+
INCREMENT BY 1
46+
NO MINVALUE
47+
NO MAXVALUE
48+
CACHE 1;
49+
ALTER SEQUENCE public.user_id_seq OWNED BY public."user".id;
50+
ALTER TABLE ONLY prisma_contract.ledger ALTER COLUMN id SET DEFAULT nextval('prisma_contract.ledger_id_seq'::regclass);
51+
ALTER TABLE ONLY public."user" ALTER COLUMN id SET DEFAULT nextval('public.user_id_seq'::regclass);
52+
INSERT INTO prisma_contract.contract VALUES ('f4e1954fd8bed87828d13c3f1a02164dc9796ef1af76ed6f98184c01263169c5', '2026-09-09 09:15:28.369559+00', '{"meta": {}, "roots": {"user": {"model": "User", "namespace": "public"}}, "domain": {"namespaces": {"public": {"models": {"User": {"fields": {"id": {"type": {"kind": "scalar", "codecId": "pg/int4@1"}, "nullable": false}, "name": {"type": {"kind": "scalar", "codecId": "pg/text@1"}, "nullable": true}, "email": {"type": {"kind": "scalar", "codecId": "pg/text@1"}, "nullable": false}}, "storage": {"table": "user", "fields": {"id": {"column": "id"}, "name": {"column": "name"}, "email": {"column": "email"}}, "namespaceId": "public"}, "relations": {}}}}}}, "target": "postgres", "storage": {"namespaces": {"public": {"id": "public", "entries": {"table": {"user": {"columns": {"id": {"codecId": "pg/int4@1", "default": {"kind": "function", "expression": "autoincrement()"}, "nullable": false, "nativeType": "int4"}, "name": {"codecId": "pg/text@1", "nullable": true, "nativeType": "text"}, "email": {"codecId": "pg/text@1", "nullable": false, "nativeType": "text"}}, "indexes": [], "uniques": [{"columns": ["email"]}], "primaryKey": {"columns": ["id"]}, "foreignKeys": []}}}}}, "storageHash": "f4e1954fd8bed87828d13c3f1a02164dc9796ef1af76ed6f98184c01263169c5"}, "extensions": {}, "profileHash": "3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2", "capabilities": {"sql": {"enums": true, "lateral": true, "returning": true, "scalarList": true, "checkConstraint": true, "defaultInInsert": true}, "postgres": {"limit": true, "jsonAgg": true, "lateral": true, "orderBy": true, "returning": true, "distinctOn": true}}, "targetFamily": "sql"}');
53+
INSERT INTO prisma_contract.ledger VALUES (1, '2026-09-09 09:15:28.369559+00', 'app', '', 'f4e1954fd8bed87828d13c3f1a02164dc9796ef1af76ed6f98184c01263169c5', '', NULL, 'f4e1954fd8bed87828d13c3f1a02164dc9796ef1af76ed6f98184c01263169c5', NULL, '[{"id": "table.user", "label": "Create table \"user\"", "target": {"id": "postgres", "details": {"name": "user", "schema": "public", "objectType": "table"}}, "execute": [{"sql": "CREATE TABLE \"public\".\"user\" (\n \"email\" text NOT NULL,\n \"id\" SERIAL NOT NULL,\n \"name\" text,\n PRIMARY KEY (\"id\")\n)", "params": [], "description": "create table \"user\""}], "summary": "Creates table \"user\"", "precheck": [{"sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", "params": ["\"public\".\"user\""], "description": "ensure table \"user\" does not exist"}], "postcheck": [{"sql": "SELECT (to_regclass($1)) IS NOT NULL AS \"result\"", "params": ["\"public\".\"user\""], "description": "verify table \"user\" exists"}], "operationClass": "additive"}, {"id": "unique.user.user_email_key", "label": "Add unique constraint on \"user\" (email)", "target": {"id": "postgres", "details": {"name": "user_email_key", "table": "user", "schema": "public", "objectType": "unique"}}, "execute": [{"sql": "ALTER TABLE \"public\".\"user\" ADD CONSTRAINT \"user_email_key\" UNIQUE (\"email\")", "description": "add unique constraint \"user_email_key\""}], "precheck": [{"sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", "params": ["user_email_key", "public", "\"public\".\"user\""], "description": "ensure constraint \"user_email_key\" does not exist"}], "postcheck": [{"sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", "params": ["user_email_key", "public", "\"public\".\"user\""], "description": "verify constraint \"user_email_key\" exists"}], "operationClass": "additive"}]');
54+
INSERT INTO prisma_contract.marker VALUES ('app', 'f4e1954fd8bed87828d13c3f1a02164dc9796ef1af76ed6f98184c01263169c5', '3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2', NULL, NULL, '2026-09-09 09:15:28.369559+00', NULL, '{}', '{}');
55+
ALTER TABLE ONLY prisma_contract.contract
56+
ADD CONSTRAINT contract_pkey PRIMARY KEY (core_hash);
57+
ALTER TABLE ONLY prisma_contract.ledger
58+
ADD CONSTRAINT ledger_pkey PRIMARY KEY (id);
59+
ALTER TABLE ONLY prisma_contract.marker
60+
ADD CONSTRAINT marker_pkey PRIMARY KEY (space);
61+
ALTER TABLE ONLY public."user"
62+
ADD CONSTRAINT user_email_key UNIQUE (email);
63+
ALTER TABLE ONLY public."user"
64+
ADD CONSTRAINT user_pkey PRIMARY KEY (id);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "node-prisma-8",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"build": "tsc",
8+
"start": "node --import ./dist/instrument.js dist/app.js",
9+
"test": "playwright test",
10+
"clean": "npx rimraf node_modules pnpm-lock.yaml dist",
11+
"test:build": "pnpm install && pnpm build",
12+
"test:assert": "pnpm test"
13+
},
14+
"dependencies": {
15+
"@prisma/orm-postgres": "8.0.0-rc.8",
16+
"@sentry/node": "file:../../packed/sentry-node-packed.tgz",
17+
"@types/express": "^4.17.21",
18+
"@types/node": "^20.19.0",
19+
"express": "^4.21.2",
20+
"typescript": "~5.9.0"
21+
},
22+
"devDependencies": {
23+
"@playwright/test": "~1.56.0",
24+
"@sentry-internal/test-utils": "link:../../../test-utils",
25+
"@sentry/core": "file:../../packed/sentry-core-packed.tgz"
26+
},
27+
"volta": {
28+
"extends": "../../package.json"
29+
}
30+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
2+
3+
const config = getPlaywrightConfig({
4+
startCommand: `pnpm start`,
5+
});
6+
7+
export default {
8+
...config,
9+
globalSetup: './global-setup.mjs',
10+
globalTeardown: './global-teardown.mjs',
11+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { randomBytes } from 'node:crypto';
2+
import postgres from '@prisma/orm-postgres/runtime';
3+
import * as Sentry from '@sentry/node';
4+
import express from 'express';
5+
import type { Contract } from './prisma/contract.d.ts';
6+
import contractJson from './prisma/contract.json' with { type: 'json' };
7+
8+
const db = postgres<Contract>({
9+
contractJson,
10+
url: 'postgresql://prisma:prisma@localhost:5438/tests',
11+
});
12+
13+
const app = express();
14+
const port = 3030;
15+
16+
app.get('/test-prisma', async (_req, res) => {
17+
const created = await db.orm.public.User.create({
18+
name: 'Tilda',
19+
email: `tilda_${randomBytes(4).toString('hex')}@sentry.io`,
20+
});
21+
22+
const users = await db.orm.public.User.all();
23+
24+
await db.orm.public.User.where(user => user.email.like('%sentry.io')).delete();
25+
26+
res.json({ created: created.id, count: users.length });
27+
});
28+
29+
Sentry.setupExpressErrorHandler(app);
30+
31+
app.listen(port, () => {
32+
console.log(`Example app listening on port ${port}`);
33+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/node';
2+
3+
Sentry.init({
4+
environment: 'qa', // dynamic sampling bias to keep transactions
5+
dsn: process.env.E2E_TEST_DSN,
6+
debug: !!process.env.DEBUG,
7+
tunnel: `http://localhost:3031/`, // proxy server
8+
tracesSampleRate: 1,
9+
});

0 commit comments

Comments
 (0)