@martel/calyx 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/bun.lock +3 -0
- package/package.json +2 -1
- package/src/cache/cache.interceptor.ts +32 -0
- package/src/cache/cache.module.ts +31 -0
- package/src/cache/cache.service.ts +86 -0
- package/src/cache/index.ts +3 -0
- package/src/graphql/decorators.ts +62 -0
- package/src/graphql/graphql.module.ts +166 -0
- package/src/graphql/index.ts +2 -0
- package/src/http/application.ts +60 -0
- package/src/http/factory.ts +1 -0
- package/src/http/router.ts +13 -0
- package/src/index.ts +3 -0
- package/src/openapi/decorators.ts +49 -0
- package/src/openapi/index.ts +2 -0
- package/src/openapi/swagger.module.ts +174 -0
- package/src/validation/compiler.ts +124 -0
- package/src/validation/decorators.ts +47 -0
- package/src/validation/index.ts +3 -0
- package/src/validation/pipe.ts +31 -0
- package/tests/cache.test.ts +93 -0
- package/tests/graphql.test.ts +112 -0
- package/tests/openapi.test.ts +95 -0
- package/tests/validation-serialization.test.ts +134 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
Module,
|
|
4
|
+
Controller,
|
|
5
|
+
Post,
|
|
6
|
+
Body,
|
|
7
|
+
CalyxFactory,
|
|
8
|
+
IsString,
|
|
9
|
+
IsNumber,
|
|
10
|
+
IsEmail,
|
|
11
|
+
IsOptional,
|
|
12
|
+
Exclude,
|
|
13
|
+
Expose,
|
|
14
|
+
ValidationPipe,
|
|
15
|
+
UsePipes,
|
|
16
|
+
} from '../src/index.ts';
|
|
17
|
+
|
|
18
|
+
// 1. DTO for Request Validation
|
|
19
|
+
class CreateUserDto {
|
|
20
|
+
@IsString()
|
|
21
|
+
name!: string;
|
|
22
|
+
|
|
23
|
+
@IsNumber()
|
|
24
|
+
age!: number;
|
|
25
|
+
|
|
26
|
+
@IsEmail()
|
|
27
|
+
email!: string;
|
|
28
|
+
|
|
29
|
+
@IsOptional()
|
|
30
|
+
@IsString()
|
|
31
|
+
bio?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. DTO for Response Serialization
|
|
35
|
+
class UserResponseDto {
|
|
36
|
+
@Expose()
|
|
37
|
+
id: number;
|
|
38
|
+
|
|
39
|
+
@Expose()
|
|
40
|
+
username: string;
|
|
41
|
+
|
|
42
|
+
@Exclude()
|
|
43
|
+
passwordHash: string;
|
|
44
|
+
|
|
45
|
+
constructor(id: number, username: string, passwordHash: string) {
|
|
46
|
+
this.id = id;
|
|
47
|
+
this.username = username;
|
|
48
|
+
this.passwordHash = passwordHash;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Controller('users')
|
|
53
|
+
class UsersController {
|
|
54
|
+
@Post('validate')
|
|
55
|
+
@UsePipes(ValidationPipe)
|
|
56
|
+
createUser(@Body() dto: CreateUserDto) {
|
|
57
|
+
return { received: true, dto };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Post('serialize')
|
|
61
|
+
getUserResponse() {
|
|
62
|
+
return new UserResponseDto(123, 'alice', 'super_secret_sha256_hash');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Module({
|
|
67
|
+
controllers: [UsersController],
|
|
68
|
+
})
|
|
69
|
+
class TestApp {}
|
|
70
|
+
|
|
71
|
+
describe('JIT Validation and Response Serialization', () => {
|
|
72
|
+
let app: any;
|
|
73
|
+
let baseUrl: string;
|
|
74
|
+
const PORT = 3922;
|
|
75
|
+
|
|
76
|
+
beforeAll(async () => {
|
|
77
|
+
app = await CalyxFactory.create(TestApp);
|
|
78
|
+
await app.listen(PORT);
|
|
79
|
+
baseUrl = `http://localhost:${PORT}`;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterAll(async () => {
|
|
83
|
+
await app.close();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('should pass validation for valid payload', async () => {
|
|
87
|
+
const res = await fetch(`${baseUrl}/users/validate`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'content-type': 'application/json' },
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
name: 'Bob',
|
|
92
|
+
age: 25,
|
|
93
|
+
email: 'bob@example.com',
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
expect(res.status).toBe(201);
|
|
97
|
+
const body = await res.json();
|
|
98
|
+
expect(body.received).toBe(true);
|
|
99
|
+
expect(body.dto).toEqual({
|
|
100
|
+
name: 'Bob',
|
|
101
|
+
age: 25,
|
|
102
|
+
email: 'bob@example.com',
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('should fail validation and return 400 with list of errors for invalid payload', async () => {
|
|
107
|
+
const res = await fetch(`${baseUrl}/users/validate`, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'content-type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
name: 123, // Should be string
|
|
112
|
+
email: 'invalid-email', // Missing @
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
expect(res.status).toBe(400);
|
|
116
|
+
const body = await res.json();
|
|
117
|
+
expect(body.message).toBe('Validation failed');
|
|
118
|
+
expect(body.errors).toContain('name must be a string');
|
|
119
|
+
expect(body.errors).toContain('age should not be empty');
|
|
120
|
+
expect(body.errors).toContain('email must be a valid email');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('should serialize response using JIT serializer and exclude excluded fields', async () => {
|
|
124
|
+
const res = await fetch(`${baseUrl}/users/serialize`, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
});
|
|
127
|
+
expect(res.status).toBe(201);
|
|
128
|
+
const body = await res.json();
|
|
129
|
+
// Excluded fields should be missing, exposed should be present
|
|
130
|
+
expect(body.id).toBe(123);
|
|
131
|
+
expect(body.username).toBe('alice');
|
|
132
|
+
expect(body.passwordHash).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
});
|