zenstack/packages/server/test/api/procedures.e2e.test.ts
Mike Willbanks 2172614e0e
custom procedures (#551)
* feat: custom procs

* chore: cleanup

* fix: remove $procedures from client

* fix: failing test due to previous alias

* feat(custom-procs)!: make procedures envelope-only via $procs

- Switch procedure calls to `db.$procs.name({ args: {...} })` (no positional args)
- Remove legacy `$procedures` alias entirely (client API + server routing/logging)
- Validate procedure envelope input (`args` object, required/unknown keys)
- Keep TanStack Query procedure hooks as `(args, options)` (with conditional args optionality)
- Update server/ORM/client tests for the envelope API

* fix: code review feedback

* fix: code review comments

* fix: coderabbit review comments

* fix: remove useless proxy method

* test: add a couple of e2e tests that verify both typing and runtime

* test: improve e2e tests

* test: add missing mutation flag

* regenerate test schema

* refactor: procedure params generation fix and type refactors

- Simplified procedure's params definition from a tuple an object, since procs are now called with an envelop now

- Refactored procedure related typing to make them more consistent with other CURD types (that usually takes the schema as the first type parameter, and a name as the second)

- Moved detailed procedure's types to "crud-types" where other ORM client detailed types are defined

- Removed some type duplication from hooks side

- Updated the "orm" sample to demonstrate procedures

* fix: disable infinite custom proc queries for now

---------

Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com>
2026-01-08 11:21:51 +08:00

86 lines
2.5 KiB
TypeScript

import type { ClientContract } from '@zenstackhq/orm';
import type { SchemaDef } from '@zenstackhq/orm/schema';
import { createTestClient } from '@zenstackhq/testtools';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { RestApiHandler } from '../../src/api/rest';
describe('Procedures E2E', () => {
let client: ClientContract<SchemaDef>;
let api: RestApiHandler;
const schema = `
datasource db {
provider = 'sqlite'
url = 'file:./test.db'
}
model User {
id Int @id @default(autoincrement())
email String @unique
}
procedure greet(name: String?): String
mutation procedure createTwoAndFail(email1: String, email2: String): Int
`;
beforeEach(async () => {
client = await createTestClient(
schema,
{
procedures: {
greet: async ({ args }: any) => {
const name = args?.name as string | undefined;
return `hello ${name ?? 'world'}`;
},
createTwoAndFail: async ({ client, args }: any) => {
const email1 = args.email1 as string;
const email2 = args.email2 as string;
await client.user.create({ data: { email: email1 } });
await client.user.create({ data: { email: email2 } });
throw new Error('boom');
},
},
} as any
);
api = new RestApiHandler({
schema: client.$schema,
endpoint: 'http://localhost/api',
pageSize: 5,
});
});
afterEach(async () => {
await client?.$disconnect();
});
it('supports $procs routes', async () => {
const r = await api.handleRequest({
client,
method: 'get',
path: '/$procs/greet',
query: { args: { name: 'alice' } } as any,
});
expect(r.status).toBe(200);
expect(r.body).toEqual({ data: 'hello alice' });
});
it('returns 422 for invalid input', async () => {
const r = await api.handleRequest({
client,
method: 'get',
path: '/$procs/greet',
query: { args: { name: 123 } } as any,
});
expect(r.status).toBe(422);
expect(r.body).toMatchObject({
errors: [
{
status: 422,
code: 'validation-error',
},
],
});
});
});