Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit fd8f9c6

Browse files
committed
test: add a couple of e2e tests that verify both typing and runtime
1 parent 055952c commit fd8f9c6

3 files changed

Lines changed: 244 additions & 74 deletions

File tree

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import type { ClientContract } from '@zenstackhq/orm';
2+
import { createTestClient } from '@zenstackhq/testtools';
3+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4+
import { schema } from '../schemas/procedures/schema';
5+
import type { User } from '../schemas/procedures/models';
6+
7+
describe('Procedures tests', () => {
8+
let client: ClientContract<typeof schema>;
9+
10+
beforeEach(async () => {
11+
client = await createTestClient(schema, {
12+
procedures: {
13+
// Query procedure that returns a single User
14+
getUser: async ({ args: { id } }) => {
15+
return await client.user.findUniqueOrThrow({
16+
where: { id },
17+
});
18+
},
19+
20+
// Query procedure that returns an array of Users
21+
listUsers: async () => {
22+
return await client.user.findMany();
23+
},
24+
25+
// Mutation procedure that creates a User
26+
signUp: async ({ client, args: { name, role } }) => {
27+
return await client.user.create({
28+
data: {
29+
name,
30+
role,
31+
},
32+
});
33+
},
34+
35+
// Query procedure that returns Void
36+
setAdmin: async ({ client, args: { userId } }) => {
37+
await client.user.update({
38+
where: { id: userId },
39+
data: { role: 'ADMIN' },
40+
});
41+
},
42+
43+
// Query procedure that returns a custom type
44+
getOverview: async () => {
45+
const userIds = await client.user.findMany({ select: { id: true } });
46+
const total = await client.user.count();
47+
return {
48+
userIds: userIds.map((u) => u.id),
49+
total,
50+
roles: ['ADMIN', 'USER'],
51+
meta: { hello: 'world' },
52+
};
53+
},
54+
55+
createMultiple: async ({ client, args: { names } }) => {
56+
return await client.$transaction(async (tx) => {
57+
const createdUsers: User[] = [];
58+
for (const name of names) {
59+
const user = await tx.user.create({
60+
data: { name },
61+
});
62+
createdUsers.push(user);
63+
}
64+
return createdUsers;
65+
});
66+
},
67+
},
68+
});
69+
});
70+
71+
afterEach(async () => {
72+
await client?.$disconnect();
73+
});
74+
75+
it('works with query proc with parameters', async () => {
76+
// Create a user first
77+
const created = await client.user.create({
78+
data: {
79+
name: 'Alice',
80+
role: 'USER',
81+
},
82+
});
83+
84+
// Call the procedure
85+
const result = await client.$procs.getUser({ args: { id: created.id } });
86+
87+
expect(result).toMatchObject({
88+
id: created.id,
89+
name: 'Alice',
90+
role: 'USER',
91+
});
92+
});
93+
94+
it('works with query proc without parameters', async () => {
95+
// Create multiple users
96+
await client.user.create({
97+
data: { name: 'Alice', role: 'USER' },
98+
});
99+
await client.user.create({
100+
data: { name: 'Bob', role: 'ADMIN' },
101+
});
102+
await client.user.create({
103+
data: { name: 'Charlie', role: 'USER' },
104+
});
105+
106+
const result = await client.$procs.listUsers();
107+
108+
expect(result).toHaveLength(3);
109+
expect(result).toEqual(
110+
expect.arrayContaining([
111+
expect.objectContaining({ name: 'Alice', role: 'USER' }),
112+
expect.objectContaining({ name: 'Bob', role: 'ADMIN' }),
113+
expect.objectContaining({ name: 'Charlie', role: 'USER' }),
114+
]),
115+
);
116+
});
117+
118+
it('works with mutation with parameters', async () => {
119+
const result = await client.$procs.signUp({ args: { name: 'Alice' } });
120+
121+
expect(result).toMatchObject({
122+
id: expect.any(Number),
123+
name: 'Alice',
124+
role: 'USER',
125+
});
126+
127+
// Verify user was created in database
128+
const users = await client.user.findMany();
129+
expect(users).toHaveLength(1);
130+
expect(users[0]).toMatchObject({
131+
name: 'Alice',
132+
role: 'USER',
133+
});
134+
135+
// accepts optional role parameter
136+
const result1 = await client.$procs.signUp({
137+
args: {
138+
name: 'Bob',
139+
role: 'ADMIN',
140+
},
141+
});
142+
143+
expect(result1).toMatchObject({
144+
id: expect.any(Number),
145+
name: 'Bob',
146+
role: 'ADMIN',
147+
});
148+
149+
// Verify user was created with correct role
150+
const user1 = await client.user.findUnique({
151+
where: { id: result1.id },
152+
});
153+
expect(user1?.role).toBe('ADMIN');
154+
});
155+
156+
it('works with mutation proc that returns void', async () => {
157+
// Create a regular user
158+
const user = await client.user.create({
159+
data: { name: 'Alice', role: 'USER' },
160+
});
161+
162+
expect(user.role).toBe('USER');
163+
164+
// Call setAdmin procedure
165+
const result = await client.$procs.setAdmin({ args: { userId: user.id } });
166+
167+
// Procedure returns void
168+
expect(result).toBeUndefined();
169+
170+
// Verify user role was updated
171+
const updated = await client.user.findUnique({
172+
where: { id: user.id },
173+
});
174+
expect(updated?.role).toBe('ADMIN');
175+
});
176+
177+
it('works with procedure returning custom type', async () => {
178+
await client.user.create({ data: { name: 'Alice', role: 'USER' } });
179+
await client.user.create({ data: { name: 'Bob', role: 'ADMIN' } });
180+
181+
const result = await client.$procs.getOverview();
182+
expect(result.total).toBe(2);
183+
expect(result.userIds).toHaveLength(2);
184+
expect(result.roles).toEqual(expect.arrayContaining(['ADMIN', 'USER']));
185+
expect(result.meta).toEqual({ hello: 'world' });
186+
});
187+
188+
it('works with transactional mutation procs', async () => {
189+
// unique constraint violation should rollback the transaction
190+
await expect(client.$procs.createMultiple({ args: { names: ['Alice', 'Alice'] } })).rejects.toThrow();
191+
await expect(client.user.count()).resolves.toBe(0);
192+
193+
// successful transaction
194+
await expect(client.$procs.createMultiple({ args: { names: ['Alice', 'Bob'] } })).resolves.toEqual(
195+
expect.arrayContaining([
196+
expect.objectContaining({ name: 'Alice' }),
197+
expect.objectContaining({ name: 'Bob' }),
198+
]),
199+
);
200+
});
201+
});

tests/e2e/orm/schemas/procedures/schema.ts

Lines changed: 37 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,21 @@ export class SchemaType implements SchemaDef {
2323
},
2424
name: {
2525
name: "name",
26-
type: "String"
26+
type: "String",
27+
unique: true,
28+
attributes: [{ name: "@unique" }]
2729
},
2830
role: {
2931
name: "role",
30-
type: "Role"
32+
type: "Role",
33+
attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal("USER") }] }],
34+
default: "USER"
3135
}
3236
},
3337
idFields: ["id"],
3438
uniqueFields: {
35-
id: { type: "Int" }
39+
id: { type: "Int" },
40+
name: { type: "String" }
3641
}
3742
}
3843
} as const;
@@ -89,83 +94,53 @@ export class SchemaType implements SchemaDef {
8994
returnType: "User",
9095
returnArray: true
9196
},
92-
findByIds: {
97+
signUp: {
9398
params: [
94-
{ name: "ids", array: true, type: "Int" }
95-
] as [
96-
ids: {
97-
"name": "ids";
98-
"type": "Int";
99-
"array": true;
100-
}
101-
],
102-
returnType: "User",
103-
returnArray: true
104-
},
105-
overview: {
106-
params: [] as [
107-
],
108-
returnType: "Overview"
109-
},
110-
getRole: {
111-
params: [] as [
112-
],
113-
returnType: "Role"
114-
},
115-
getRoles: {
116-
params: [] as [
117-
],
118-
returnType: "Role",
119-
returnArray: true
120-
},
121-
getNull: {
122-
params: [] as [
123-
],
124-
returnType: "Null"
125-
},
126-
getVoid: {
127-
params: [] as [
128-
],
129-
returnType: "Void"
130-
},
131-
getUndefined: {
132-
params: [] as [
133-
],
134-
returnType: "Undefined"
135-
},
136-
greet: {
137-
params: [
138-
{ name: "name", optional: true, type: "String" }
99+
{ name: "name", type: "String" },
100+
{ name: "role", optional: true, type: "Role" }
139101
] as [
140102
name: {
141103
"name": "name";
142104
"type": "String";
105+
},
106+
role: {
107+
"name": "role";
108+
"type": "Role";
143109
"optional": true;
144110
}
145111
],
146-
returnType: "String"
112+
returnType: "User",
113+
mutation: true
147114
},
148-
echoJson: {
115+
setAdmin: {
149116
params: [
150-
{ name: "payload", type: "Json" }
117+
{ name: "userId", type: "Int" }
151118
] as [
152-
payload: {
153-
"name": "payload";
154-
"type": "Json";
119+
userId: {
120+
"name": "userId";
121+
"type": "Int";
155122
}
156123
],
157-
returnType: "Json"
124+
returnType: "Void"
125+
},
126+
getOverview: {
127+
params: [] as [
128+
],
129+
returnType: "Overview"
158130
},
159-
echoBytes: {
131+
createMultiple: {
160132
params: [
161-
{ name: "payload", type: "Bytes" }
133+
{ name: "names", array: true, type: "String" }
162134
] as [
163-
payload: {
164-
"name": "payload";
165-
"type": "Bytes";
135+
names: {
136+
"name": "names";
137+
"type": "String";
138+
"array": true;
166139
}
167140
],
168-
returnType: "Bytes"
141+
returnType: "User",
142+
returnArray: true,
143+
mutation: true
169144
}
170145
} as const;
171146
plugins = {};

tests/e2e/orm/schemas/procedures/schema.zmodel

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,13 @@ type Overview {
1717

1818
model User {
1919
id Int @id @default(autoincrement())
20-
name String
21-
role Role
20+
name String @unique
21+
role Role @default(USER)
2222
}
2323

2424
procedure getUser(id: Int): User
2525
procedure listUsers(): User[]
26-
procedure findByIds(ids: Int[]): User[]
27-
procedure overview(): Overview
28-
procedure getRole(): Role
29-
procedure getRoles(): Role[]
30-
procedure getNull(): Null
31-
procedure getVoid(): Void
32-
procedure getUndefined(): Undefined
33-
procedure greet(name: String?): String
34-
procedure echoJson(payload: Json): Json
35-
procedure echoBytes(payload: Bytes): Bytes
26+
mutation procedure signUp(name: String, role: Role?): User
27+
procedure setAdmin(userId: Int): Void
28+
procedure getOverview(): Overview
29+
mutation procedure createMultiple(names: String[]): User[]

0 commit comments

Comments
 (0)