Skip to content

Commit 7338c11

Browse files
author
=
committed
chore: add tests and validations
1 parent 1cabdf9 commit 7338c11

3 files changed

Lines changed: 193 additions & 0 deletions

File tree

packages/language/src/validators/function-invocation-validator.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,20 @@ export default class FunctionInvocationValidator implements AstValidator<Express
237237
}
238238
}
239239

240+
@func('customId')
241+
private _checkCustomId(expr: InvocationExpr, accept: ValidationAcceptor) {
242+
// first argument must be positive if provided
243+
const lengthArg = expr.args[0]?.value;
244+
if (lengthArg) {
245+
const length = getLiteral<number>(lengthArg);
246+
if (length !== undefined && length <= 0) {
247+
accept('error', 'first argument must be a positive number', {
248+
node: expr.args[0]!,
249+
});
250+
}
251+
}
252+
}
253+
240254
@func('auth')
241255
private _checkAuth(expr: InvocationExpr, accept: ValidationAcceptor) {
242256
if (!expr.$resolvedType) {

packages/language/test/function-invocation.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,4 +414,22 @@ describe('Function Invocation Tests', () => {
414414
);
415415
});
416416
});
417+
418+
describe('customId() length validation', () => {
419+
it('should reject non-positive lengths', async () => {
420+
await loadSchemaWithError(
421+
`
422+
datasource db {
423+
provider = 'sqlite'
424+
url = 'file:./dev.db'
425+
}
426+
427+
model User {
428+
id String @id @default(customId(-1))
429+
}
430+
`,
431+
'first argument must be a positive number',
432+
);
433+
});
434+
});
417435
});
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { createTestClient } from '@zenstackhq/testtools';
2+
import { describe, expect, it } from 'vitest';
3+
4+
const schema = `
5+
model User {
6+
id String @id @default(customId())
7+
posts Post[]
8+
}
9+
10+
model Post {
11+
id String @id @default(customId())
12+
userId String?
13+
user User? @relation(fields: [userId], references: [id])
14+
comments Comment[]
15+
}
16+
17+
model Comment {
18+
id String @id @default(customId())
19+
postId String?
20+
post Post? @relation(fields: [postId], references: [id])
21+
}
22+
`;
23+
24+
describe('customId', async () => {
25+
it('works with no arguments', async () => {
26+
const client = await createTestClient(schema, {
27+
customId: ({ model, length }) => `${model}.${length ?? 16}`,
28+
});
29+
30+
await expect(client.user.create({ data: {} })).resolves.toMatchObject({
31+
id: 'User.16',
32+
});
33+
34+
await expect(client.post.create({ data: {} })).resolves.toMatchObject({
35+
id: 'Post.16',
36+
});
37+
38+
await expect(client.comment.create({ data: {} })).resolves.toMatchObject({
39+
id: 'Comment.16',
40+
});
41+
});
42+
43+
it('works with arguments', async () => {
44+
const schema = `
45+
model User {
46+
id String @id @default(customId(8))
47+
posts Post[]
48+
}
49+
50+
model Post {
51+
id String @id @default(customId(8))
52+
userId String?
53+
user User? @relation(fields: [userId], references: [id])
54+
comments Comment[]
55+
}
56+
57+
model Comment {
58+
id String @id @default(customId(8))
59+
postId String?
60+
post Post? @relation(fields: [postId], references: [id])
61+
}
62+
`;
63+
64+
const client = await createTestClient(schema, {
65+
customId: ({ model, length }) => `${model}.${length}`,
66+
});
67+
68+
await expect(client.user.create({ data: {} })).resolves.toMatchObject({
69+
id: 'User.8',
70+
});
71+
72+
await expect(client.post.create({ data: {} })).resolves.toMatchObject({
73+
id: 'Post.8',
74+
});
75+
76+
await expect(client.comment.create({ data: {} })).resolves.toMatchObject({
77+
id: 'Comment.8',
78+
});
79+
});
80+
81+
it('works with nested', async () => {
82+
const client = await createTestClient(schema, {
83+
customId: ({ model, length }) => `${model}.${length ?? 16}`,
84+
});
85+
86+
await expect(client.user.create({
87+
data: {
88+
posts: {
89+
create: {},
90+
},
91+
},
92+
})).resolves.toMatchObject({
93+
id: 'User.16',
94+
});
95+
96+
await expect(client.post.findUnique({
97+
where: {
98+
id: 'Post.16',
99+
}
100+
})).resolves.toBeTruthy();
101+
});
102+
103+
it('works with deeply nested', async () => {
104+
const client = await createTestClient(schema, {
105+
customId: ({ model, length }) => `${model}.${length ?? 16}`,
106+
});
107+
108+
await expect(client.user.create({
109+
data: {
110+
posts: {
111+
create: {
112+
comments: {
113+
create: {},
114+
},
115+
},
116+
},
117+
},
118+
})).resolves.toMatchObject({
119+
id: 'User.16',
120+
});
121+
122+
await expect(client.post.findUnique({
123+
where: {
124+
id: 'Post.16',
125+
}
126+
})).resolves.toBeTruthy();
127+
128+
await expect(client.comment.findUnique({
129+
where: {
130+
id: 'Comment.16',
131+
}
132+
})).resolves.toBeTruthy();
133+
});
134+
135+
it('rejects without an implementation', async () => {
136+
const client = await createTestClient(schema);
137+
await expect(client.user.create({ data: {} })).rejects.toThrowError('implementation not provided');
138+
});
139+
140+
it('rejects without a valid implementation', async () => {
141+
// @ts-expect-error
142+
let client = await createTestClient(schema, {
143+
customId: () => undefined,
144+
});
145+
// @ts-expect-error
146+
await expect(client.user.create({ data: {} })).rejects.toThrowError('non-empty string');
147+
148+
client = await createTestClient(schema, {
149+
customId: () => '',
150+
});
151+
// @ts-expect-error
152+
await expect(client.user.create({ data: {} })).rejects.toThrowError('non-empty string');
153+
154+
// @ts-expect-error
155+
client = await createTestClient(schema, {
156+
customId: () => 1,
157+
});
158+
// @ts-expect-error
159+
await expect(client.user.create({ data: {} })).rejects.toThrowError('non-empty string');
160+
});
161+
});

0 commit comments

Comments
 (0)