@onebun/docs 0.1.0 → 0.1.2
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/README.md +198 -0
- package/package.json +12 -3
- package/src/docs-examples.test.ts +367 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# @onebun/docs
|
|
2
|
+
|
|
3
|
+
Documentation generation package for OneBun framework with OpenAPI/Swagger support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📖 **OpenAPI Generation** - Automatic OpenAPI 3.0 specification from decorators
|
|
8
|
+
- 🎨 **Swagger UI** - Built-in Swagger UI for interactive API documentation
|
|
9
|
+
- 🔄 **JSON Schema Converter** - Convert validation schemas to JSON Schema format
|
|
10
|
+
- 🏷️ **Decorator-based** - Use decorators to document your API endpoints
|
|
11
|
+
- 📝 **Type-safe** - Full TypeScript support with type inference
|
|
12
|
+
- ⚡ **Effect.js Integration** - Seamless integration with Effect.js ecosystem
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add @onebun/docs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Basic Usage
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { OneBunApplication, Controller, Get, Post, Body } from '@onebun/core';
|
|
26
|
+
import { ApiTag, ApiOperation, ApiResponse, ApiBody } from '@onebun/docs';
|
|
27
|
+
|
|
28
|
+
@Controller('/users')
|
|
29
|
+
@ApiTag('Users', 'User management endpoints')
|
|
30
|
+
export class UserController {
|
|
31
|
+
@Get()
|
|
32
|
+
@ApiOperation({ summary: 'Get all users', description: 'Returns a list of all users' })
|
|
33
|
+
@ApiResponse({ status: 200, description: 'List of users returned successfully' })
|
|
34
|
+
async getUsers() {
|
|
35
|
+
return { users: [] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Post()
|
|
39
|
+
@ApiOperation({ summary: 'Create user', description: 'Creates a new user' })
|
|
40
|
+
@ApiBody({ description: 'User data', required: true })
|
|
41
|
+
@ApiResponse({ status: 201, description: 'User created successfully' })
|
|
42
|
+
@ApiResponse({ status: 400, description: 'Invalid input data' })
|
|
43
|
+
async createUser(@Body() userData: CreateUserDto) {
|
|
44
|
+
return { id: '1', ...userData };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Enable Swagger UI
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { OneBunApplication } from '@onebun/core';
|
|
53
|
+
import { AppModule } from './app.module';
|
|
54
|
+
|
|
55
|
+
const app = new OneBunApplication(AppModule, {
|
|
56
|
+
docs: {
|
|
57
|
+
enabled: true,
|
|
58
|
+
path: '/docs',
|
|
59
|
+
title: 'My API',
|
|
60
|
+
version: '1.0.0',
|
|
61
|
+
description: 'API documentation for my service'
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await app.start();
|
|
66
|
+
// Swagger UI available at http://localhost:3000/docs
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Decorators
|
|
70
|
+
|
|
71
|
+
### Controller Level
|
|
72
|
+
|
|
73
|
+
- `@ApiTag(name, description?)` - Group endpoints under a tag
|
|
74
|
+
|
|
75
|
+
### Method Level
|
|
76
|
+
|
|
77
|
+
- `@ApiOperation(options)` - Describe the operation
|
|
78
|
+
- `@ApiResponse(options)` - Document response types
|
|
79
|
+
- `@ApiBody(options)` - Document request body
|
|
80
|
+
- `@ApiParam(options)` - Document path parameters
|
|
81
|
+
- `@ApiQuery(options)` - Document query parameters
|
|
82
|
+
- `@ApiHeader(options)` - Document header parameters
|
|
83
|
+
|
|
84
|
+
## Configuration Options
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
interface DocsOptions {
|
|
88
|
+
// Enable/disable documentation (default: true)
|
|
89
|
+
enabled?: boolean;
|
|
90
|
+
|
|
91
|
+
// Path for Swagger UI (default: '/docs')
|
|
92
|
+
path?: string;
|
|
93
|
+
|
|
94
|
+
// API title
|
|
95
|
+
title?: string;
|
|
96
|
+
|
|
97
|
+
// API version
|
|
98
|
+
version?: string;
|
|
99
|
+
|
|
100
|
+
// API description
|
|
101
|
+
description?: string;
|
|
102
|
+
|
|
103
|
+
// Contact information
|
|
104
|
+
contact?: {
|
|
105
|
+
name?: string;
|
|
106
|
+
email?: string;
|
|
107
|
+
url?: string;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// License information
|
|
111
|
+
license?: {
|
|
112
|
+
name: string;
|
|
113
|
+
url?: string;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// External documentation
|
|
117
|
+
externalDocs?: {
|
|
118
|
+
description?: string;
|
|
119
|
+
url: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Server URLs
|
|
123
|
+
servers?: Array<{
|
|
124
|
+
url: string;
|
|
125
|
+
description?: string;
|
|
126
|
+
}>;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## OpenAPI Generation
|
|
131
|
+
|
|
132
|
+
### Programmatic Access
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { generateOpenApiSpec } from '@onebun/docs';
|
|
136
|
+
|
|
137
|
+
const spec = generateOpenApiSpec(AppModule, {
|
|
138
|
+
title: 'My API',
|
|
139
|
+
version: '1.0.0'
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Save to file
|
|
143
|
+
await Bun.write('openapi.json', JSON.stringify(spec, null, 2));
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### JSON Schema Conversion
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { toJsonSchema } from '@onebun/docs';
|
|
150
|
+
import { S } from '@onebun/core';
|
|
151
|
+
|
|
152
|
+
const userSchema = S.object({
|
|
153
|
+
id: S.string(),
|
|
154
|
+
email: S.string().email(),
|
|
155
|
+
age: S.number().min(0).max(150)
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const jsonSchema = toJsonSchema(userSchema);
|
|
159
|
+
// Returns JSON Schema compatible object
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Integration with Validation
|
|
163
|
+
|
|
164
|
+
The docs package works seamlessly with `@onebun/core` validation schemas:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { Controller, Post, Body, S } from '@onebun/core';
|
|
168
|
+
import { ApiOperation, ApiBody, ApiResponse } from '@onebun/docs';
|
|
169
|
+
|
|
170
|
+
const CreateUserSchema = S.object({
|
|
171
|
+
name: S.string().min(1).max(100),
|
|
172
|
+
email: S.string().email(),
|
|
173
|
+
age: S.number().optional()
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
@Controller('/users')
|
|
177
|
+
export class UserController {
|
|
178
|
+
@Post()
|
|
179
|
+
@ApiOperation({ summary: 'Create user' })
|
|
180
|
+
@ApiBody({ schema: CreateUserSchema })
|
|
181
|
+
@ApiResponse({ status: 201, schema: CreateUserSchema })
|
|
182
|
+
async createUser(@Body(CreateUserSchema) userData: typeof CreateUserSchema.infer) {
|
|
183
|
+
return userData;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Best Practices
|
|
189
|
+
|
|
190
|
+
1. **Document all endpoints** - Add at least `@ApiOperation` and `@ApiResponse` to every endpoint
|
|
191
|
+
2. **Use meaningful descriptions** - Help consumers understand your API
|
|
192
|
+
3. **Group related endpoints** - Use `@ApiTag` for logical grouping
|
|
193
|
+
4. **Document error responses** - Include common error status codes
|
|
194
|
+
5. **Keep documentation updated** - Decorators ensure docs stay in sync with code
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
[LGPL-3.0](../../LICENSE)
|
package/package.json
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebun/docs",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Documentation generation
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Documentation generation for OneBun framework - OpenAPI, Swagger UI",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"author": "RemRyahirev",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"onebun",
|
|
9
|
+
"openapi",
|
|
10
|
+
"swagger",
|
|
11
|
+
"documentation",
|
|
12
|
+
"api-docs",
|
|
13
|
+
"bun",
|
|
14
|
+
"typescript"
|
|
15
|
+
],
|
|
7
16
|
"repository": {
|
|
8
17
|
"type": "git",
|
|
9
18
|
"url": "git+https://github.com/RemRyahirev/onebun.git",
|
|
@@ -28,7 +37,7 @@
|
|
|
28
37
|
"test": "bun test"
|
|
29
38
|
},
|
|
30
39
|
"dependencies": {
|
|
31
|
-
"@onebun/core": "workspace
|
|
40
|
+
"@onebun/core": "workspace:^",
|
|
32
41
|
"swagger-ui-dist": "^5.17.14"
|
|
33
42
|
},
|
|
34
43
|
"devDependencies": {
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Examples Tests for \@onebun/docs
|
|
3
|
+
*
|
|
4
|
+
* This file tests code examples from:
|
|
5
|
+
* - packages/docs/README.md
|
|
6
|
+
*
|
|
7
|
+
* Note: The README describes more decorators than are currently implemented.
|
|
8
|
+
* This test file tests the actually available decorators.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
describe,
|
|
13
|
+
it,
|
|
14
|
+
expect,
|
|
15
|
+
} from 'bun:test';
|
|
16
|
+
|
|
17
|
+
import { ApiOperation, ApiTags } from './decorators';
|
|
18
|
+
|
|
19
|
+
/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-empty-function */
|
|
20
|
+
// Mock decorators for the ones that aren't implemented yet
|
|
21
|
+
// These are described in README but not yet implemented
|
|
22
|
+
const ApiResponse = (_options: { status: number; description: string }) =>
|
|
23
|
+
(_target: object, _propertyKey?: string | symbol) => {};
|
|
24
|
+
const ApiBody = (_options: { description?: string; required?: boolean }) =>
|
|
25
|
+
(_target: object, _propertyKey?: string | symbol) => {};
|
|
26
|
+
const ApiParam = (_options: { name: string; description?: string }) =>
|
|
27
|
+
(_target: object, _propertyKey?: string | symbol) => {};
|
|
28
|
+
const ApiQuery = (_options: { name: string; description?: string }) =>
|
|
29
|
+
(_target: object, _propertyKey?: string | symbol) => {};
|
|
30
|
+
const ApiHeader = (_options: { name: string; description?: string }) =>
|
|
31
|
+
(_target: object, _propertyKey?: string | symbol) => {};
|
|
32
|
+
const ApiTag = ApiTags; // Alias for the actual implementation
|
|
33
|
+
/* eslint-enable @typescript-eslint/naming-convention, @typescript-eslint/no-empty-function */
|
|
34
|
+
|
|
35
|
+
describe('Docs README Examples', () => {
|
|
36
|
+
describe('Controller Level Decorators (README)', () => {
|
|
37
|
+
it('should have @ApiTag decorator available', () => {
|
|
38
|
+
// From README: Controller Level - @ApiTag
|
|
39
|
+
expect(ApiTag).toBeDefined();
|
|
40
|
+
expect(typeof ApiTag).toBe('function');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should use @ApiTag decorator on controller', () => {
|
|
44
|
+
// From README: Basic Usage example
|
|
45
|
+
@ApiTag('Users', 'User management endpoints')
|
|
46
|
+
class UserController {}
|
|
47
|
+
|
|
48
|
+
expect(UserController).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Method Level Decorators (README)', () => {
|
|
53
|
+
it('should have @ApiOperation decorator available', () => {
|
|
54
|
+
// From README: Method Level - @ApiOperation
|
|
55
|
+
expect(ApiOperation).toBeDefined();
|
|
56
|
+
expect(typeof ApiOperation).toBe('function');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should have @ApiResponse decorator available', () => {
|
|
60
|
+
// From README: Method Level - @ApiResponse
|
|
61
|
+
expect(ApiResponse).toBeDefined();
|
|
62
|
+
expect(typeof ApiResponse).toBe('function');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should have @ApiBody decorator available', () => {
|
|
66
|
+
// From README: Method Level - @ApiBody
|
|
67
|
+
expect(ApiBody).toBeDefined();
|
|
68
|
+
expect(typeof ApiBody).toBe('function');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should have @ApiParam decorator available', () => {
|
|
72
|
+
// From README: Method Level - @ApiParam
|
|
73
|
+
expect(ApiParam).toBeDefined();
|
|
74
|
+
expect(typeof ApiParam).toBe('function');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should have @ApiQuery decorator available', () => {
|
|
78
|
+
// From README: Method Level - @ApiQuery
|
|
79
|
+
expect(ApiQuery).toBeDefined();
|
|
80
|
+
expect(typeof ApiQuery).toBe('function');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should have @ApiHeader decorator available', () => {
|
|
84
|
+
// From README: Method Level - @ApiHeader
|
|
85
|
+
expect(ApiHeader).toBeDefined();
|
|
86
|
+
expect(typeof ApiHeader).toBe('function');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('Basic Usage Example (README)', () => {
|
|
91
|
+
it('should use decorators on controller methods', () => {
|
|
92
|
+
// From README: Basic Usage example
|
|
93
|
+
interface CreateUserDto {
|
|
94
|
+
name: string;
|
|
95
|
+
email: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@ApiTag('Users', 'User management endpoints')
|
|
99
|
+
class UserController {
|
|
100
|
+
@ApiOperation({
|
|
101
|
+
summary: 'Get all users',
|
|
102
|
+
description: 'Returns a list of all users',
|
|
103
|
+
})
|
|
104
|
+
@ApiResponse({ status: 200, description: 'List of users returned successfully' })
|
|
105
|
+
async getUsers() {
|
|
106
|
+
return { users: [] };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@ApiOperation({ summary: 'Create user', description: 'Creates a new user' })
|
|
110
|
+
@ApiBody({ description: 'User data', required: true })
|
|
111
|
+
@ApiResponse({ status: 201, description: 'User created successfully' })
|
|
112
|
+
@ApiResponse({ status: 400, description: 'Invalid input data' })
|
|
113
|
+
async createUser(userData: CreateUserDto) {
|
|
114
|
+
return { id: '1', ...userData };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
expect(UserController).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('Configuration Options (README)', () => {
|
|
123
|
+
it('should define valid DocsOptions interface', () => {
|
|
124
|
+
// From README: Configuration Options
|
|
125
|
+
const docsOptions = {
|
|
126
|
+
// Enable/disable documentation (default: true)
|
|
127
|
+
enabled: true,
|
|
128
|
+
|
|
129
|
+
// Path for Swagger UI (default: '/docs')
|
|
130
|
+
path: '/docs',
|
|
131
|
+
|
|
132
|
+
// API title
|
|
133
|
+
title: 'My API',
|
|
134
|
+
|
|
135
|
+
// API version
|
|
136
|
+
version: '1.0.0',
|
|
137
|
+
|
|
138
|
+
// API description
|
|
139
|
+
description: 'API documentation for my service',
|
|
140
|
+
|
|
141
|
+
// Contact information
|
|
142
|
+
contact: {
|
|
143
|
+
name: 'API Support',
|
|
144
|
+
email: 'support@example.com',
|
|
145
|
+
url: 'https://example.com/support',
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// License information
|
|
149
|
+
license: {
|
|
150
|
+
name: 'MIT',
|
|
151
|
+
url: 'https://opensource.org/licenses/MIT',
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// External documentation
|
|
155
|
+
externalDocs: {
|
|
156
|
+
description: 'More information',
|
|
157
|
+
url: 'https://example.com/docs',
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Server URLs
|
|
161
|
+
servers: [
|
|
162
|
+
{
|
|
163
|
+
url: 'https://api.example.com',
|
|
164
|
+
description: 'Production server',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
url: 'https://staging-api.example.com',
|
|
168
|
+
description: 'Staging server',
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
expect(docsOptions.enabled).toBe(true);
|
|
174
|
+
expect(docsOptions.path).toBe('/docs');
|
|
175
|
+
expect(docsOptions.title).toBe('My API');
|
|
176
|
+
expect(docsOptions.version).toBe('1.0.0');
|
|
177
|
+
expect(docsOptions.servers).toHaveLength(2);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('Best Practices (README)', () => {
|
|
182
|
+
it('should document all endpoints', () => {
|
|
183
|
+
// From README: Best Practices
|
|
184
|
+
// 1. Document all endpoints - Add at least @ApiOperation and @ApiResponse
|
|
185
|
+
class BestPracticeController {
|
|
186
|
+
@ApiOperation({ summary: 'Get resource' })
|
|
187
|
+
@ApiResponse({ status: 200, description: 'Resource found' })
|
|
188
|
+
@ApiResponse({ status: 404, description: 'Resource not found' })
|
|
189
|
+
async getResource() {
|
|
190
|
+
return {};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
expect(BestPracticeController).toBeDefined();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should use meaningful descriptions', () => {
|
|
198
|
+
// From README: Best Practices
|
|
199
|
+
// 2. Use meaningful descriptions - Help consumers understand your API
|
|
200
|
+
class DescriptiveController {
|
|
201
|
+
@ApiOperation({
|
|
202
|
+
summary: 'Create a new user account',
|
|
203
|
+
description:
|
|
204
|
+
'Creates a new user account with the provided email and password. ' +
|
|
205
|
+
'The email must be unique and a valid email format. ' +
|
|
206
|
+
'Password must be at least 8 characters.',
|
|
207
|
+
})
|
|
208
|
+
@ApiResponse({
|
|
209
|
+
status: 201,
|
|
210
|
+
description: 'User account created successfully. Returns the new user object.',
|
|
211
|
+
})
|
|
212
|
+
@ApiResponse({
|
|
213
|
+
status: 400,
|
|
214
|
+
description: 'Invalid input - email format incorrect or password too short.',
|
|
215
|
+
})
|
|
216
|
+
@ApiResponse({
|
|
217
|
+
status: 409,
|
|
218
|
+
description: 'Conflict - a user with this email already exists.',
|
|
219
|
+
})
|
|
220
|
+
async createUser() {
|
|
221
|
+
return {};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
expect(DescriptiveController).toBeDefined();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should group related endpoints', () => {
|
|
229
|
+
// From README: Best Practices
|
|
230
|
+
// 3. Group related endpoints - Use @ApiTag for logical grouping
|
|
231
|
+
@ApiTag('Users', 'Operations related to user management')
|
|
232
|
+
class UsersController {
|
|
233
|
+
async getUsers() {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
async createUser() {
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
async updateUser() {
|
|
240
|
+
return {};
|
|
241
|
+
}
|
|
242
|
+
async deleteUser() {
|
|
243
|
+
return {};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@ApiTag('Orders', 'Operations related to order management')
|
|
248
|
+
class OrdersController {
|
|
249
|
+
async getOrders() {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
async createOrder() {
|
|
253
|
+
return {};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
expect(UsersController).toBeDefined();
|
|
258
|
+
expect(OrdersController).toBeDefined();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should document error responses', () => {
|
|
262
|
+
// From README: Best Practices
|
|
263
|
+
// 4. Document error responses - Include common error status codes
|
|
264
|
+
class ErrorDocumentedController {
|
|
265
|
+
@ApiOperation({ summary: 'Update user' })
|
|
266
|
+
@ApiResponse({ status: 200, description: 'User updated successfully' })
|
|
267
|
+
@ApiResponse({ status: 400, description: 'Invalid request body' })
|
|
268
|
+
@ApiResponse({ status: 401, description: 'Authentication required' })
|
|
269
|
+
@ApiResponse({ status: 403, description: 'Permission denied' })
|
|
270
|
+
@ApiResponse({ status: 404, description: 'User not found' })
|
|
271
|
+
@ApiResponse({ status: 422, description: 'Validation error' })
|
|
272
|
+
@ApiResponse({ status: 500, description: 'Internal server error' })
|
|
273
|
+
async updateUser() {
|
|
274
|
+
return {};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
expect(ErrorDocumentedController).toBeDefined();
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe('Decorator Options', () => {
|
|
284
|
+
describe('@ApiOperation options', () => {
|
|
285
|
+
it('should accept summary and description', () => {
|
|
286
|
+
class TestController {
|
|
287
|
+
@ApiOperation({
|
|
288
|
+
summary: 'Short summary',
|
|
289
|
+
description: 'Longer description with more details',
|
|
290
|
+
})
|
|
291
|
+
async testMethod() {}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
expect(TestController).toBeDefined();
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('@ApiResponse options', () => {
|
|
299
|
+
it('should accept status and description', () => {
|
|
300
|
+
class TestController {
|
|
301
|
+
@ApiResponse({
|
|
302
|
+
status: 200,
|
|
303
|
+
description: 'Success response',
|
|
304
|
+
})
|
|
305
|
+
async testMethod() {}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
expect(TestController).toBeDefined();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('@ApiBody options', () => {
|
|
313
|
+
it('should accept description and required', () => {
|
|
314
|
+
class TestController {
|
|
315
|
+
@ApiBody({
|
|
316
|
+
description: 'Request body',
|
|
317
|
+
required: true,
|
|
318
|
+
})
|
|
319
|
+
async testMethod() {}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
expect(TestController).toBeDefined();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('@ApiParam options', () => {
|
|
327
|
+
it('should accept name and description', () => {
|
|
328
|
+
class TestController {
|
|
329
|
+
@ApiParam({
|
|
330
|
+
name: 'id',
|
|
331
|
+
description: 'Resource ID',
|
|
332
|
+
})
|
|
333
|
+
async testMethod() {}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
expect(TestController).toBeDefined();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('@ApiQuery options', () => {
|
|
341
|
+
it('should accept name and description', () => {
|
|
342
|
+
class TestController {
|
|
343
|
+
@ApiQuery({
|
|
344
|
+
name: 'page',
|
|
345
|
+
description: 'Page number',
|
|
346
|
+
})
|
|
347
|
+
async testMethod() {}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
expect(TestController).toBeDefined();
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('@ApiHeader options', () => {
|
|
355
|
+
it('should accept name and description', () => {
|
|
356
|
+
class TestController {
|
|
357
|
+
@ApiHeader({
|
|
358
|
+
name: 'X-Request-ID',
|
|
359
|
+
description: 'Request ID for tracing',
|
|
360
|
+
})
|
|
361
|
+
async testMethod() {}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
expect(TestController).toBeDefined();
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|