nats-micro
Version:
NATS micro compatible extra-lightweight microservice library
261 lines (204 loc) • 7.25 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { expect } from 'chai';
import { zodToJsonSchema } from 'zod-to-json-schema';
import {
Microservice, method, microservice,
z, MicroserviceSchema, BrokerResponse, Request, Response,
} from '../../src/index.js';
import { broker } from '../common.js';
const testRequestSchema = z.object({
field1: z.string().optional(),
field2: z.number({ description: 'a number' }).min(15),
});
type TestRequest =
z.infer<typeof testRequestSchema>;
const testRequestJsonSchema = zodToJsonSchema(testRequestSchema);
const testResponseSchema = z.object({
success: z.boolean(),
});
type TestResponse =
z.infer<typeof testResponseSchema>;
const testResponseJsonSchema = zodToJsonSchema(testResponseSchema);
const voidJsonSchema = zodToJsonSchema(z.void());
describe('type schemas', function () {
this.afterEach(function () {
broker.offAll();
});
it('request+response typed method', async function () {
class Test {
public test(req: Request<TestRequest>, res: Response<TestResponse>): void {
res.send({
success: req.data.field1 === 'user',
});
}
}
const service = await Microservice.createFromClass(broker, new Test());
const reqSchema = service.discovery.getMethodJsonSchema('test', 'request');
const resSchema = service.discovery.getMethodJsonSchema('test', 'response');
expect(reqSchema).to.eql(testRequestJsonSchema);
expect(resSchema).to.eql(testResponseJsonSchema);
});
it('request+response typed async method', async function () {
class Test {
public test(req: Request<TestRequest>, res: Response<TestResponse>): void {
res.send({
success: req.data.field1 === 'user',
});
}
}
const service = await Microservice.createFromClass(broker, new Test());
const reqSchema = service.discovery.getMethodJsonSchema('test', 'request');
const resSchema = service.discovery.getMethodJsonSchema('test', 'response');
expect(reqSchema).to.eql(testRequestJsonSchema);
expect(resSchema).to.eql(testResponseJsonSchema);
});
it('request-only typed method', async function () {
class Test {
public test(_req: Request<TestRequest>, res: Response<void>): void {
res.sendNoResponse();
}
}
const service = await Microservice.createFromClass(broker, new Test());
const reqSchema = service.discovery.getMethodJsonSchema('test', 'request');
const resSchema = service.discovery.getMethodJsonSchema('test', 'response');
expect(reqSchema).to.eql(testRequestJsonSchema);
expect(resSchema).to.eql(voidJsonSchema);
});
it('non-typed method', async function () {
class Test {
public test(_req: Request<void>, res: Response<void>): void {
res.sendNoResponse();
}
}
const service = await Microservice.createFromClass(broker, new Test());
const reqSchema = service.discovery.getMethodJsonSchema('test', 'request');
const resSchema = service.discovery.getMethodJsonSchema('test', 'response');
expect(reqSchema).to.eql(voidJsonSchema);
expect(resSchema).to.eql(voidJsonSchema);
});
it('getting schemas via broker', async function () {
class Test {
public test(req: Request<TestRequest>, res: Response<TestResponse>): void {
res.send({
success: req.data.field1 === 'user',
});
}
}
await Microservice.createFromClass(broker, new Test());
const data: BrokerResponse<MicroserviceSchema | undefined> = await broker.request('$SRV.SCHEMA', '');
expect(data.data).to.exist;
const methodSchema = data.data!.endpoints.find((ep) => ep.name === 'test');
expect(methodSchema).to.exist;
expect(methodSchema!.schema.request).to.eql(testRequestJsonSchema);
expect(methodSchema!.schema.response).to.eql(testResponseJsonSchema);
});
describe('request validation', function () {
it('non-json', async function () {
class Test {
public test(req: Request<TestRequest>, res: Response<TestResponse>): void {
res.send({
success: req.data.field1 === 'user',
});
}
}
await Microservice.createFromClass(broker, new Test());
await expect(broker.request('test.test', '')).to.be.rejectedWith(/request/i);
});
it('invalid type', async function () {
class Test {
public test(req: Request<TestRequest>, res: Response<TestResponse>): void {
res.send({
success: req.data.field1 === 'user',
});
}
}
await Microservice.createFromClass(broker, new Test());
await expect(broker.request('test.test', { name: 'no name' })).to.be.rejectedWith(/request/i);
});
});
describe('response validation', function () {
it('invalid type', async function () {
class Test {
public test(_req: Request<void>, res: Response<TestResponse>): void {
res.send(
{
hello: 'invalid',
} as any,
);
}
}
await Microservice.createFromClass(broker, new Test());
await expect(broker.request('test.test', '')).to.be.rejectedWith(/response/i);
});
it('invalid empty response', async function () {
class Test {
public test(_req: Request<void>, res: Response<TestResponse>): void {
res.send(undefined as any);
}
}
await Microservice.createFromClass(broker, new Test());
await expect(broker.request('test.test', '')).to.be.rejectedWith(/response/i);
});
// eslint-disable-next-line mocha/no-identical-title
it('non-empty response', async function () {
class Test {
public test(_req: Request<void>, res: Response<void>): void {
res.send(
{
hello: 'invalid',
} as any,
);
}
}
await Microservice.createFromClass(broker, new Test());
await expect(broker.request('test.test', { name: 'no name' })).to.be.rejectedWith(/response/i);
});
});
});