@rx-ted/packages-honest-plugins 1.0.1
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 +13 -0
- package/README.md +45 -0
- package/api-doc/.turbo/turbo-build.log +1 -0
- package/api-doc/.turbo/turbo-test.log +11 -0
- package/api-doc/.turbo/turbo-typecheck.log +1 -0
- package/api-doc/CHANGELOG.md +22 -0
- package/api-doc/README.md +80 -0
- package/api-doc/dist/api-doc.plugin.d.ts +53 -0
- package/api-doc/dist/api-doc.plugin.js +452 -0
- package/api-doc/dist/index.d.ts +5 -0
- package/api-doc/dist/index.js +3 -0
- package/api-doc/dist/interfaces/renderer.interface.d.ts +7 -0
- package/api-doc/dist/interfaces/renderer.interface.js +1 -0
- package/api-doc/dist/renderers/scalar.renderer.d.ts +10 -0
- package/api-doc/dist/renderers/scalar.renderer.js +76 -0
- package/api-doc/dist/renderers/swagger.renderer.d.ts +10 -0
- package/api-doc/dist/renderers/swagger.renderer.js +83 -0
- package/api-doc/node_modules/.bin/esbuild +22 -0
- package/api-doc/node_modules/.bin/jiti +22 -0
- package/api-doc/node_modules/.bin/sass +22 -0
- package/api-doc/node_modules/.bin/tsx +22 -0
- package/api-doc/node_modules/.bin/vite +22 -0
- package/api-doc/node_modules/.bin/vitest +22 -0
- package/api-doc/node_modules/.bin/yaml +22 -0
- package/api-doc/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/api-doc/package.json +48 -0
- package/api-doc/src/api-doc.plugin.ts +554 -0
- package/api-doc/src/index.ts +5 -0
- package/api-doc/src/interfaces/renderer.interface.ts +5 -0
- package/api-doc/src/plugin.test.ts +740 -0
- package/api-doc/src/renderers/scalar.renderer.ts +82 -0
- package/api-doc/src/renderers/swagger.renderer.ts +89 -0
- package/api-doc/tsconfig.json +21 -0
- package/api-doc/vitest.config.ts +13 -0
- package/cache/.turbo/turbo-build.log +1 -0
- package/cache/.turbo/turbo-test.log +16 -0
- package/cache/CHANGELOG.md +23 -0
- package/cache/README.md +79 -0
- package/cache/dist/cache-service.d.ts +8 -0
- package/cache/dist/cache-service.js +21 -0
- package/cache/dist/cacheable.d.ts +2 -0
- package/cache/dist/cacheable.js +10 -0
- package/cache/dist/cloudflare/driver.d.ts +23 -0
- package/cache/dist/cloudflare/driver.js +102 -0
- package/cache/dist/cloudflare/index.d.ts +3 -0
- package/cache/dist/cloudflare/index.js +2 -0
- package/cache/dist/cloudflare/plugin.d.ts +19 -0
- package/cache/dist/cloudflare/plugin.js +58 -0
- package/cache/dist/index.d.ts +11 -0
- package/cache/dist/index.js +14 -0
- package/cache/dist/local/driver.d.ts +2 -0
- package/cache/dist/local/driver.js +98 -0
- package/cache/dist/local/index.d.ts +2 -0
- package/cache/dist/local/index.js +2 -0
- package/cache/dist/local/plugin.d.ts +14 -0
- package/cache/dist/local/plugin.js +24 -0
- package/cache/dist/redis/driver.d.ts +10 -0
- package/cache/dist/redis/driver.js +102 -0
- package/cache/dist/redis/index.d.ts +4 -0
- package/cache/dist/redis/index.js +2 -0
- package/cache/dist/redis/plugin.d.ts +25 -0
- package/cache/dist/redis/plugin.js +61 -0
- package/cache/dist/resolve.d.ts +24 -0
- package/cache/dist/resolve.js +114 -0
- package/cache/dist/types.d.ts +18 -0
- package/cache/dist/types.js +1 -0
- package/cache/node_modules/.bin/esbuild +22 -0
- package/cache/node_modules/.bin/jiti +22 -0
- package/cache/node_modules/.bin/sass +22 -0
- package/cache/node_modules/.bin/tsx +22 -0
- package/cache/node_modules/.bin/vite +22 -0
- package/cache/node_modules/.bin/vitest +22 -0
- package/cache/node_modules/.bin/yaml +22 -0
- package/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/cache/package.json +46 -0
- package/cache/src/cache-service.test.ts +16 -0
- package/cache/src/cache-service.ts +15 -0
- package/cache/src/cacheable.test.ts +78 -0
- package/cache/src/cacheable.ts +16 -0
- package/cache/src/cloudflare/driver.test.ts +188 -0
- package/cache/src/cloudflare/driver.ts +118 -0
- package/cache/src/cloudflare/index.ts +3 -0
- package/cache/src/cloudflare/plugin.ts +71 -0
- package/cache/src/index.test.ts +68 -0
- package/cache/src/index.ts +20 -0
- package/cache/src/local/driver.ts +117 -0
- package/cache/src/local/index.ts +2 -0
- package/cache/src/local/plugin.ts +34 -0
- package/cache/src/redis/driver.test.ts +71 -0
- package/cache/src/redis/driver.ts +124 -0
- package/cache/src/redis/index.ts +4 -0
- package/cache/src/redis/plugin.ts +84 -0
- package/cache/src/resolve.test.ts +257 -0
- package/cache/src/resolve.ts +140 -0
- package/cache/src/types.ts +14 -0
- package/cache/tsconfig.json +21 -0
- package/cache/vitest.config.ts +13 -0
- package/db/.turbo/turbo-build.log +1 -0
- package/db/.turbo/turbo-test.log +21 -0
- package/db/CHANGELOG.md +26 -0
- package/db/README.md +99 -0
- package/db/dist/constants.d.ts +1 -0
- package/db/dist/constants.js +1 -0
- package/db/dist/d1/index.d.ts +3 -0
- package/db/dist/d1/index.js +2 -0
- package/db/dist/d1/plugin.d.ts +18 -0
- package/db/dist/d1/plugin.js +104 -0
- package/db/dist/db-service.d.ts +7 -0
- package/db/dist/db-service.js +21 -0
- package/db/dist/index.d.ts +15 -0
- package/db/dist/index.js +20 -0
- package/db/dist/mysql/client.d.ts +36 -0
- package/db/dist/mysql/client.js +84 -0
- package/db/dist/mysql/driver.d.ts +18 -0
- package/db/dist/mysql/driver.js +107 -0
- package/db/dist/mysql/index.d.ts +11 -0
- package/db/dist/mysql/index.js +11 -0
- package/db/dist/mysql/plugin.d.ts +51 -0
- package/db/dist/mysql/plugin.js +94 -0
- package/db/dist/mysql/types.d.ts +38 -0
- package/db/dist/mysql/types.js +1 -0
- package/db/dist/postgres/index.d.ts +2 -0
- package/db/dist/postgres/index.js +3 -0
- package/db/dist/repository.d.ts +14 -0
- package/db/dist/repository.js +79 -0
- package/db/dist/resolve.d.ts +6 -0
- package/db/dist/resolve.js +81 -0
- package/db/dist/schema-builder/column-builder.d.ts +26 -0
- package/db/dist/schema-builder/column-builder.js +53 -0
- package/db/dist/schema-builder/compile-d1.d.ts +5 -0
- package/db/dist/schema-builder/compile-d1.js +67 -0
- package/db/dist/schema-builder/compile-mysql.d.ts +12 -0
- package/db/dist/schema-builder/compile-mysql.js +78 -0
- package/db/dist/schema-builder/index.d.ts +8 -0
- package/db/dist/schema-builder/index.js +22 -0
- package/db/dist/schema-builder/table-builder.d.ts +16 -0
- package/db/dist/schema-builder/table-builder.js +38 -0
- package/db/dist/schema-builder/types.d.ts +26 -0
- package/db/dist/schema-builder/types.js +1 -0
- package/db/dist/schema-builder/zod-bridge.d.ts +24 -0
- package/db/dist/schema-builder/zod-bridge.js +53 -0
- package/db/dist/sqlite/index.d.ts +4 -0
- package/db/dist/sqlite/index.js +2 -0
- package/db/dist/sqlite/plugin.d.ts +17 -0
- package/db/dist/sqlite/plugin.js +33 -0
- package/db/dist/types.d.ts +42 -0
- package/db/dist/types.js +1 -0
- package/db/node_modules/.bin/esbuild +22 -0
- package/db/node_modules/.bin/jiti +22 -0
- package/db/node_modules/.bin/sass +22 -0
- package/db/node_modules/.bin/tsx +22 -0
- package/db/node_modules/.bin/vite +22 -0
- package/db/node_modules/.bin/vitest +22 -0
- package/db/node_modules/.bin/yaml +22 -0
- package/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/db/package.json +59 -0
- package/db/src/constants.test.ts +8 -0
- package/db/src/constants.ts +1 -0
- package/db/src/d1/index.ts +13 -0
- package/db/src/d1/plugin.ts +135 -0
- package/db/src/db-service.ts +14 -0
- package/db/src/index.test.ts +63 -0
- package/db/src/index.ts +57 -0
- package/db/src/mysql/client.ts +112 -0
- package/db/src/mysql/driver.test.ts +190 -0
- package/db/src/mysql/driver.ts +116 -0
- package/db/src/mysql/index.ts +51 -0
- package/db/src/mysql/plugin.test.ts +228 -0
- package/db/src/mysql/plugin.ts +128 -0
- package/db/src/mysql/types.ts +32 -0
- package/db/src/postgres/index.ts +15 -0
- package/db/src/repository.test.ts +195 -0
- package/db/src/repository.ts +95 -0
- package/db/src/resolve.ts +105 -0
- package/db/src/schema-builder/column-builder.test.ts +55 -0
- package/db/src/schema-builder/column-builder.ts +87 -0
- package/db/src/schema-builder/compile-d1.test.ts +60 -0
- package/db/src/schema-builder/compile-d1.ts +70 -0
- package/db/src/schema-builder/compile-mysql.test.ts +64 -0
- package/db/src/schema-builder/compile-mysql.ts +102 -0
- package/db/src/schema-builder/index.test.ts +36 -0
- package/db/src/schema-builder/index.ts +41 -0
- package/db/src/schema-builder/table-builder.test.ts +55 -0
- package/db/src/schema-builder/table-builder.ts +50 -0
- package/db/src/schema-builder/types.test.ts +68 -0
- package/db/src/schema-builder/types.ts +30 -0
- package/db/src/schema-builder/zod-bridge.ts +89 -0
- package/db/src/sqlite/index.ts +14 -0
- package/db/src/sqlite/plugin.ts +46 -0
- package/db/src/types.ts +45 -0
- package/db/tsconfig.json +21 -0
- package/db/vitest.config.ts +13 -0
- package/mail/.turbo/turbo-build.log +1 -0
- package/mail/.turbo/turbo-test.log +12 -0
- package/mail/.turbo/turbo-typecheck.log +1 -0
- package/mail/CHANGELOG.md +25 -0
- package/mail/README.md +214 -0
- package/mail/dist/index.d.ts +4 -0
- package/mail/dist/index.js +2 -0
- package/mail/dist/plugin.d.ts +64 -0
- package/mail/dist/plugin.js +232 -0
- package/mail/dist/providers/brevo.provider.d.ts +15 -0
- package/mail/dist/providers/brevo.provider.js +75 -0
- package/mail/dist/providers/custom.provider.d.ts +9 -0
- package/mail/dist/providers/custom.provider.js +22 -0
- package/mail/dist/providers/resend.provider.d.ts +16 -0
- package/mail/dist/providers/resend.provider.js +77 -0
- package/mail/dist/providers/smtp.provider.d.ts +21 -0
- package/mail/dist/providers/smtp.provider.js +86 -0
- package/mail/dist/rate-limiter.d.ts +16 -0
- package/mail/dist/rate-limiter.js +49 -0
- package/mail/dist/types.d.ts +68 -0
- package/mail/dist/types.js +6 -0
- package/mail/node_modules/.bin/esbuild +22 -0
- package/mail/node_modules/.bin/jiti +22 -0
- package/mail/node_modules/.bin/sass +22 -0
- package/mail/node_modules/.bin/tsx +22 -0
- package/mail/node_modules/.bin/vite +22 -0
- package/mail/node_modules/.bin/vitest +22 -0
- package/mail/node_modules/.bin/yaml +22 -0
- package/mail/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/mail/package.json +52 -0
- package/mail/src/index.ts +4 -0
- package/mail/src/plugin.test.ts +219 -0
- package/mail/src/plugin.ts +313 -0
- package/mail/src/providers/brevo.provider.ts +78 -0
- package/mail/src/providers/custom.provider.ts +29 -0
- package/mail/src/providers/resend.provider.ts +74 -0
- package/mail/src/providers/smtp.provider.ts +85 -0
- package/mail/src/rate-limiter.test.ts +81 -0
- package/mail/src/rate-limiter.ts +54 -0
- package/mail/src/types.ts +81 -0
- package/mail/tsconfig.json +21 -0
- package/mail/vitest.config.ts +13 -0
- package/package.json +36 -0
- package/s3/.turbo/turbo-build.log +1 -0
- package/s3/.turbo/turbo-test.log +11 -0
- package/s3/CHANGELOG.md +20 -0
- package/s3/README.md +90 -0
- package/s3/dist/index.d.ts +6 -0
- package/s3/dist/index.js +4 -0
- package/s3/dist/s3-service.d.ts +7 -0
- package/s3/dist/s3-service.js +21 -0
- package/s3/dist/s3.driver.d.ts +6 -0
- package/s3/dist/s3.driver.js +221 -0
- package/s3/dist/s3.plugin.d.ts +24 -0
- package/s3/dist/s3.plugin.js +58 -0
- package/s3/dist/types.d.ts +30 -0
- package/s3/dist/types.js +1 -0
- package/s3/node_modules/.bin/esbuild +22 -0
- package/s3/node_modules/.bin/jiti +22 -0
- package/s3/node_modules/.bin/sass +22 -0
- package/s3/node_modules/.bin/tsx +22 -0
- package/s3/node_modules/.bin/vite +22 -0
- package/s3/node_modules/.bin/vitest +22 -0
- package/s3/node_modules/.bin/yaml +22 -0
- package/s3/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/s3/package.json +47 -0
- package/s3/src/index.test.ts +20 -0
- package/s3/src/index.ts +13 -0
- package/s3/src/s3-service.ts +14 -0
- package/s3/src/s3.driver.ts +259 -0
- package/s3/src/s3.plugin.ts +80 -0
- package/s3/src/types.ts +37 -0
- package/s3/tsconfig.json +21 -0
- package/s3/vitest.config.ts +13 -0
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
Controller,
|
|
5
|
+
Get,
|
|
6
|
+
Post,
|
|
7
|
+
createTestApplication,
|
|
8
|
+
createTestingModule,
|
|
9
|
+
VERSION_NEUTRAL,
|
|
10
|
+
} from '@rx-ted/packages-honest';
|
|
11
|
+
import { ApiDocPlugin, normalizeRoute, type ApiDocPluginOptions } from './api-doc.plugin';
|
|
12
|
+
import { ScalarRenderer } from './renderers/scalar.renderer';
|
|
13
|
+
import { SwaggerRenderer } from './renderers/swagger.renderer';
|
|
14
|
+
import type { IDocRenderer } from './interfaces/renderer.interface';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
|
|
17
|
+
@Controller('/items', { tag: { name: 'Items', description: 'Item management endpoints' } })
|
|
18
|
+
class ItemsController {
|
|
19
|
+
@Get('/', {
|
|
20
|
+
apiDoc: {
|
|
21
|
+
summary: 'List all items',
|
|
22
|
+
description: 'Returns a paginated list of items',
|
|
23
|
+
tags: ['Items'],
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
list() {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Get('/:id', {
|
|
31
|
+
apiDoc: {
|
|
32
|
+
summary: 'Get item by ID',
|
|
33
|
+
tags: ['Items'],
|
|
34
|
+
request: {
|
|
35
|
+
params: z.object({ id: z.string() }),
|
|
36
|
+
},
|
|
37
|
+
responses: {
|
|
38
|
+
200: { description: 'Item found' },
|
|
39
|
+
404: { description: 'Item not found' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
getById() {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Post('/', {
|
|
48
|
+
apiDoc: {
|
|
49
|
+
summary: 'Create item',
|
|
50
|
+
tags: ['Items'],
|
|
51
|
+
request: {
|
|
52
|
+
body: z.object({ name: z.string().min(1), price: z.number().min(0) }),
|
|
53
|
+
},
|
|
54
|
+
responses: {
|
|
55
|
+
201: { description: 'Created' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
create() {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Get('/search', {
|
|
64
|
+
apiDoc: {
|
|
65
|
+
summary: 'Search items',
|
|
66
|
+
tags: ['Items'],
|
|
67
|
+
request: {
|
|
68
|
+
query: z.object({ q: z.string(), page: z.number().optional() }),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
search() {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Get('/admin', {
|
|
77
|
+
apiDoc: {
|
|
78
|
+
summary: 'Admin endpoint',
|
|
79
|
+
tags: ['Admin'],
|
|
80
|
+
security: [{ bearerAuth: [] }],
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
admin() {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Controller('/healthz', { tag: { name: 'System', description: 'System health endpoints' } })
|
|
89
|
+
class HealthController {
|
|
90
|
+
@Get('/', {
|
|
91
|
+
apiDoc: {
|
|
92
|
+
summary: 'Health check',
|
|
93
|
+
tags: ['System'],
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
check() {
|
|
97
|
+
return { ok: true };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Controller('/no-doc')
|
|
102
|
+
class NoDocController {
|
|
103
|
+
@Get('/')
|
|
104
|
+
plain() {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const TestAppModule = createTestingModule({
|
|
110
|
+
controllers: [ItemsController, HealthController, NoDocController],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const mockRenderer: IDocRenderer = {
|
|
114
|
+
name: 'mock',
|
|
115
|
+
renderHtml: ({ specUrl, uiTitle }) => `<html>${specUrl}-${uiTitle}</html>`,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function createPlugin(options: ApiDocPluginOptions = {}) {
|
|
119
|
+
return new ApiDocPlugin(options);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
describe('ApiDocPlugin', () => {
|
|
123
|
+
describe('constructor', () => {
|
|
124
|
+
it('should set default options', () => {
|
|
125
|
+
const plugin = createPlugin();
|
|
126
|
+
expect(plugin.name).toBe('api-doc-plugin');
|
|
127
|
+
expect(plugin.version).toBe('1.0.0');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should accept custom options', () => {
|
|
131
|
+
const plugin = createPlugin({
|
|
132
|
+
specUrl: '/spec.json',
|
|
133
|
+
uiRoute: '/api-docs',
|
|
134
|
+
uiTitle: 'My API',
|
|
135
|
+
defaultRenderer: 'swagger',
|
|
136
|
+
});
|
|
137
|
+
expect((plugin as any).specUrl).toBe('/spec.json');
|
|
138
|
+
expect((plugin as any).uiRoute).toBe('/api-docs');
|
|
139
|
+
expect((plugin as any).uiTitle).toBe('My API');
|
|
140
|
+
expect((plugin as any).defaultRendererName).toBe('swagger');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should register custom renderers', () => {
|
|
144
|
+
const plugin = createPlugin({ renderers: [mockRenderer] });
|
|
145
|
+
expect(plugin.getRenderer('mock')).toBe(mockRenderer);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should register default renderers when none provided', () => {
|
|
149
|
+
const plugin = createPlugin();
|
|
150
|
+
expect(plugin.getRenderer('scalar')).toBeInstanceOf(ScalarRenderer);
|
|
151
|
+
expect(plugin.getRenderer('swagger')).toBeInstanceOf(SwaggerRenderer);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('setRoutingOptions', () => {
|
|
156
|
+
it('should store routing options', () => {
|
|
157
|
+
const plugin = createPlugin();
|
|
158
|
+
plugin.setRoutingOptions({ prefix: 'api', version: 1 });
|
|
159
|
+
expect((plugin as any).routingPrefix).toBe('api');
|
|
160
|
+
expect((plugin as any).routingVersion).toBe(1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should handle undefined options', () => {
|
|
164
|
+
const plugin = createPlugin();
|
|
165
|
+
plugin.setRoutingOptions(undefined as any);
|
|
166
|
+
expect((plugin as any).routingPrefix).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('registerRenderer / getRenderer', () => {
|
|
171
|
+
it('should register and retrieve renderer', () => {
|
|
172
|
+
const plugin = createPlugin();
|
|
173
|
+
plugin.registerRenderer(mockRenderer);
|
|
174
|
+
expect(plugin.getRenderer('mock')).toBe(mockRenderer);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should return undefined for unknown renderer', () => {
|
|
178
|
+
const plugin = createPlugin();
|
|
179
|
+
expect(plugin.getRenderer('nonexistent')).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('normalizeRoute', () => {
|
|
184
|
+
it('should normalize route paths', () => {
|
|
185
|
+
expect(normalizeRoute('/docs')).toBe('/docs');
|
|
186
|
+
expect(normalizeRoute('docs')).toBe('/docs');
|
|
187
|
+
expect(normalizeRoute('/docs/')).toBe('/docs');
|
|
188
|
+
expect(normalizeRoute('')).toBe('/');
|
|
189
|
+
expect(normalizeRoute('/')).toBe('/');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('convertHonoPathToOpenAPI', () => {
|
|
194
|
+
it('should convert :param to {param}', () => {
|
|
195
|
+
const plugin = createPlugin();
|
|
196
|
+
expect((plugin as any).convertHonoPathToOpenAPI('/users/:id')).toBe('/users/{id}');
|
|
197
|
+
expect((plugin as any).convertHonoPathToOpenAPI('/:a/:b')).toBe('/{a}/{b}');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should leave non-param paths unchanged', () => {
|
|
201
|
+
const plugin = createPlugin();
|
|
202
|
+
expect((plugin as any).convertHonoPathToOpenAPI('/users/list')).toBe('/users/list');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('isPathExcluded', () => {
|
|
207
|
+
it('should exclude matching paths', () => {
|
|
208
|
+
const plugin = createPlugin();
|
|
209
|
+
expect((plugin as any).isPathExcluded('/health', ['/health'])).toBe(true);
|
|
210
|
+
expect((plugin as any).isPathExcluded('/health/details', ['/health'])).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should support wildcard patterns', () => {
|
|
214
|
+
const plugin = createPlugin();
|
|
215
|
+
expect((plugin as any).isPathExcluded('/internal/secret', ['/internal/*'])).toBe(true);
|
|
216
|
+
expect((plugin as any).isPathExcluded('/internal', ['/internal/*'])).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('applyPrefixToUrl', () => {
|
|
221
|
+
it('should return url unchanged when no routing options', () => {
|
|
222
|
+
const plugin = createPlugin();
|
|
223
|
+
expect((plugin as any).applyPrefixToUrl('/docs')).toBe('/docs');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should apply prefix', () => {
|
|
227
|
+
const plugin = createPlugin();
|
|
228
|
+
plugin.setRoutingOptions({ prefix: 'api', version: VERSION_NEUTRAL });
|
|
229
|
+
expect((plugin as any).applyPrefixToUrl('/docs')).toBe('/api/docs');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should apply version', () => {
|
|
233
|
+
const plugin = createPlugin();
|
|
234
|
+
plugin.setRoutingOptions({ prefix: 'api', version: 1 });
|
|
235
|
+
expect((plugin as any).applyPrefixToUrl('/docs')).toBe('/api/v1/docs');
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('toOpenAPISchema', () => {
|
|
240
|
+
it('should handle plain object schema', () => {
|
|
241
|
+
const plugin = createPlugin();
|
|
242
|
+
const schema = (plugin as any).toOpenAPISchema({ type: 'string' });
|
|
243
|
+
expect(schema).toEqual({ type: 'string' });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should handle null/undefined', () => {
|
|
247
|
+
const plugin = createPlugin();
|
|
248
|
+
expect((plugin as any).toOpenAPISchema(null)).toEqual({ type: 'object' });
|
|
249
|
+
expect((plugin as any).toOpenAPISchema(undefined)).toEqual({ type: 'object' });
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should handle Zod schemas', () => {
|
|
253
|
+
const plugin = createPlugin();
|
|
254
|
+
const result = (plugin as any).toOpenAPISchema(z.string());
|
|
255
|
+
expect(result).toEqual({ type: 'string' });
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('zodToOpenAPI', () => {
|
|
260
|
+
it('should convert ZodString', () => {
|
|
261
|
+
const plugin = createPlugin();
|
|
262
|
+
expect((plugin as any).zodToOpenAPI(z.string())).toEqual({ type: 'string' });
|
|
263
|
+
expect((plugin as any).zodToOpenAPI(z.string().min(3).max(100))).toEqual({
|
|
264
|
+
type: 'string',
|
|
265
|
+
minLength: 3,
|
|
266
|
+
maxLength: 100,
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should convert ZodNumber', () => {
|
|
271
|
+
const plugin = createPlugin();
|
|
272
|
+
expect((plugin as any).zodToOpenAPI(z.number())).toEqual({ type: 'number' });
|
|
273
|
+
expect((plugin as any).zodToOpenAPI(z.number().min(0).max(100))).toEqual({
|
|
274
|
+
type: 'number',
|
|
275
|
+
minimum: 0,
|
|
276
|
+
maximum: 100,
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should convert ZodBoolean', () => {
|
|
281
|
+
const plugin = createPlugin();
|
|
282
|
+
expect((plugin as any).zodToOpenAPI(z.boolean())).toEqual({ type: 'boolean' });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should convert ZodArray', () => {
|
|
286
|
+
const plugin = createPlugin();
|
|
287
|
+
expect((plugin as any).zodToOpenAPI(z.array(z.string()))).toEqual({
|
|
288
|
+
type: 'array',
|
|
289
|
+
items: { type: 'string' },
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should convert ZodObject', () => {
|
|
294
|
+
const plugin = createPlugin();
|
|
295
|
+
const schema = z.object({ name: z.string(), age: z.number().optional() });
|
|
296
|
+
const result = (plugin as any).zodToOpenAPI(schema);
|
|
297
|
+
expect(result).toEqual({
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: {
|
|
300
|
+
name: { type: 'string' },
|
|
301
|
+
age: { type: 'number' },
|
|
302
|
+
},
|
|
303
|
+
required: ['name'],
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should convert ZodEnum', () => {
|
|
308
|
+
const plugin = createPlugin();
|
|
309
|
+
expect((plugin as any).zodToOpenAPI(z.enum(['a', 'b', 'c']))).toEqual({
|
|
310
|
+
type: 'string',
|
|
311
|
+
enum: ['a', 'b', 'c'],
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should convert ZodOptional', () => {
|
|
316
|
+
const plugin = createPlugin();
|
|
317
|
+
expect((plugin as any).zodToOpenAPI(z.string().optional())).toEqual({ type: 'string' });
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should convert ZodUnion', () => {
|
|
321
|
+
const plugin = createPlugin();
|
|
322
|
+
const result = (plugin as any).zodToOpenAPI(z.union([z.string(), z.number()]));
|
|
323
|
+
expect(result).toEqual({
|
|
324
|
+
oneOf: [{ type: 'string' }, { type: 'number' }],
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('spec generation', () => {
|
|
330
|
+
it('should generate spec with routes from controllers', async () => {
|
|
331
|
+
const plugin = createPlugin({
|
|
332
|
+
specUrl: '/openapi.json',
|
|
333
|
+
uiRoute: '/docs',
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const testApp = await createTestApplication({
|
|
337
|
+
module: TestAppModule,
|
|
338
|
+
appOptions: {
|
|
339
|
+
plugins: [plugin],
|
|
340
|
+
debug: false,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const response = await testApp.request('/openapi.json');
|
|
345
|
+
expect(response.status).toBe(200);
|
|
346
|
+
|
|
347
|
+
const envelope = await response.json();
|
|
348
|
+
const spec = envelope.data;
|
|
349
|
+
|
|
350
|
+
expect(spec.openapi).toBe('3.0.0');
|
|
351
|
+
expect(spec.info.title).toBe('API Documentation');
|
|
352
|
+
expect(spec.paths).toBeDefined();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should have expected routes in spec', async () => {
|
|
356
|
+
const plugin = createPlugin({
|
|
357
|
+
specUrl: '/openapi.json',
|
|
358
|
+
securitySchemes: {
|
|
359
|
+
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
|
|
360
|
+
},
|
|
361
|
+
security: [{ bearerAuth: [] }],
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const testApp = await createTestApplication({
|
|
365
|
+
module: TestAppModule,
|
|
366
|
+
appOptions: {
|
|
367
|
+
plugins: [plugin],
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const response = await testApp.request('/openapi.json');
|
|
372
|
+
const envelope = await response.json();
|
|
373
|
+
const spec = envelope.data;
|
|
374
|
+
|
|
375
|
+
// Main endpoints
|
|
376
|
+
expect(spec.paths['/items']?.get).toBeDefined();
|
|
377
|
+
expect(spec.paths['/items']?.post).toBeDefined();
|
|
378
|
+
expect(spec.paths['/items/{id}']?.get).toBeDefined();
|
|
379
|
+
expect(spec.paths['/items/search']?.get).toBeDefined();
|
|
380
|
+
expect(spec.paths['/healthz']?.get).toBeDefined();
|
|
381
|
+
|
|
382
|
+
// No-doc route should still appear (just without summary)
|
|
383
|
+
expect(spec.paths['/no-doc']?.get).toBeDefined();
|
|
384
|
+
expect(spec.paths['/no-doc'].get.summary).toBeUndefined();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should include tags in spec', async () => {
|
|
388
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
389
|
+
|
|
390
|
+
const testApp = await createTestApplication({
|
|
391
|
+
module: TestAppModule,
|
|
392
|
+
appOptions: {
|
|
393
|
+
plugins: [plugin],
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const response = await testApp.request('/openapi.json');
|
|
398
|
+
const envelope = await response.json();
|
|
399
|
+
const spec = envelope.data;
|
|
400
|
+
|
|
401
|
+
expect(spec.tags).toBeDefined();
|
|
402
|
+
const tagNames = spec.tags.map((t: any) => t.name);
|
|
403
|
+
expect(tagNames).toContain('Items');
|
|
404
|
+
expect(tagNames).toContain('System');
|
|
405
|
+
|
|
406
|
+
// Items routes should have tag
|
|
407
|
+
expect(spec.paths['/items']?.get?.tags).toEqual(['Items']);
|
|
408
|
+
expect(spec.paths['/healthz']?.get?.tags).toEqual(['System']);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('should include security schemes in spec', async () => {
|
|
412
|
+
const plugin = createPlugin({
|
|
413
|
+
specUrl: '/openapi.json',
|
|
414
|
+
securitySchemes: {
|
|
415
|
+
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
|
|
416
|
+
},
|
|
417
|
+
security: [{ bearerAuth: [] }],
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const testApp = await createTestApplication({
|
|
421
|
+
module: TestAppModule,
|
|
422
|
+
appOptions: {
|
|
423
|
+
plugins: [plugin],
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const response = await testApp.request('/openapi.json');
|
|
428
|
+
const envelope = await response.json();
|
|
429
|
+
const spec = envelope.data;
|
|
430
|
+
|
|
431
|
+
expect(spec.components?.securitySchemes?.bearerAuth).toBeDefined();
|
|
432
|
+
expect(spec.components.securitySchemes.bearerAuth).toEqual({
|
|
433
|
+
type: 'http',
|
|
434
|
+
scheme: 'bearer',
|
|
435
|
+
bearerFormat: 'JWT',
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should include parameter schemas from Zod', async () => {
|
|
440
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
441
|
+
|
|
442
|
+
const testApp = await createTestApplication({
|
|
443
|
+
module: TestAppModule,
|
|
444
|
+
appOptions: {
|
|
445
|
+
plugins: [plugin],
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const response = await testApp.request('/openapi.json');
|
|
450
|
+
const envelope = await response.json();
|
|
451
|
+
const spec = envelope.data;
|
|
452
|
+
|
|
453
|
+
const getById = spec.paths['/items/{id}']?.get;
|
|
454
|
+
const params = getById?.parameters ?? [];
|
|
455
|
+
|
|
456
|
+
expect(params).toEqual(
|
|
457
|
+
expect.arrayContaining([
|
|
458
|
+
expect.objectContaining({
|
|
459
|
+
name: 'id',
|
|
460
|
+
in: 'path',
|
|
461
|
+
required: true,
|
|
462
|
+
schema: { type: 'string' },
|
|
463
|
+
}),
|
|
464
|
+
]),
|
|
465
|
+
);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('should include request body schema', async () => {
|
|
469
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
470
|
+
|
|
471
|
+
const testApp = await createTestApplication({
|
|
472
|
+
module: TestAppModule,
|
|
473
|
+
appOptions: {
|
|
474
|
+
plugins: [plugin],
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const response = await testApp.request('/openapi.json');
|
|
479
|
+
const envelope = await response.json();
|
|
480
|
+
const spec = envelope.data;
|
|
481
|
+
|
|
482
|
+
const postItem = spec.paths['/items']?.post;
|
|
483
|
+
expect(postItem.requestBody).toBeDefined();
|
|
484
|
+
expect(postItem.requestBody.content['application/json'].schema).toEqual({
|
|
485
|
+
type: 'object',
|
|
486
|
+
properties: {
|
|
487
|
+
name: { type: 'string', minLength: 1 },
|
|
488
|
+
price: { type: 'number', minimum: 0 },
|
|
489
|
+
},
|
|
490
|
+
required: ['name', 'price'],
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should include query parameter schemas', async () => {
|
|
495
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
496
|
+
|
|
497
|
+
const testApp = await createTestApplication({
|
|
498
|
+
module: TestAppModule,
|
|
499
|
+
appOptions: {
|
|
500
|
+
plugins: [plugin],
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const response = await testApp.request('/openapi.json');
|
|
505
|
+
const envelope = await response.json();
|
|
506
|
+
const spec = envelope.data;
|
|
507
|
+
|
|
508
|
+
const searchRoute = spec.paths['/items/search']?.get;
|
|
509
|
+
const params = searchRoute?.parameters ?? [];
|
|
510
|
+
|
|
511
|
+
expect(params).toEqual(
|
|
512
|
+
expect.arrayContaining([
|
|
513
|
+
expect.objectContaining({
|
|
514
|
+
name: 'q',
|
|
515
|
+
in: 'query',
|
|
516
|
+
required: true,
|
|
517
|
+
}),
|
|
518
|
+
expect.objectContaining({
|
|
519
|
+
name: 'page',
|
|
520
|
+
in: 'query',
|
|
521
|
+
required: false,
|
|
522
|
+
}),
|
|
523
|
+
]),
|
|
524
|
+
);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('should respect per-route security override', async () => {
|
|
528
|
+
const plugin = createPlugin({
|
|
529
|
+
specUrl: '/openapi.json',
|
|
530
|
+
securitySchemes: {
|
|
531
|
+
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
|
|
532
|
+
},
|
|
533
|
+
security: [{ bearerAuth: [] }],
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const testApp = await createTestApplication({
|
|
537
|
+
module: TestAppModule,
|
|
538
|
+
appOptions: {
|
|
539
|
+
plugins: [plugin],
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const response = await testApp.request('/openapi.json');
|
|
544
|
+
const envelope = await response.json();
|
|
545
|
+
const spec = envelope.data;
|
|
546
|
+
|
|
547
|
+
// Admin route has per-route security override
|
|
548
|
+
expect(spec.paths['/items/admin']?.get?.security).toEqual([{ bearerAuth: [] }]);
|
|
549
|
+
|
|
550
|
+
// Other routes use default security
|
|
551
|
+
expect(spec.paths['/items']?.get?.security).toEqual([{ bearerAuth: [] }]);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should include response schemas', async () => {
|
|
555
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
556
|
+
|
|
557
|
+
const testApp = await createTestApplication({
|
|
558
|
+
module: TestAppModule,
|
|
559
|
+
appOptions: {
|
|
560
|
+
plugins: [plugin],
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const response = await testApp.request('/openapi.json');
|
|
565
|
+
const envelope = await response.json();
|
|
566
|
+
const spec = envelope.data;
|
|
567
|
+
|
|
568
|
+
const getById = spec.paths['/items/{id}']?.get;
|
|
569
|
+
expect(getById.responses).toBeDefined();
|
|
570
|
+
expect(getById.responses['200']).toEqual({ description: 'Item found' });
|
|
571
|
+
expect(getById.responses['404']).toEqual({ description: 'Item not found' });
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
describe('exclusions', () => {
|
|
576
|
+
it('should exclude specified paths', async () => {
|
|
577
|
+
const plugin = createPlugin({
|
|
578
|
+
specUrl: '/openapi.json',
|
|
579
|
+
exclude: ['/healthz'],
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
const testApp = await createTestApplication({
|
|
583
|
+
module: TestAppModule,
|
|
584
|
+
appOptions: {
|
|
585
|
+
plugins: [plugin],
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const response = await testApp.request('/openapi.json');
|
|
590
|
+
const envelope = await response.json();
|
|
591
|
+
const spec = envelope.data;
|
|
592
|
+
|
|
593
|
+
expect(spec.paths['/healthz']).toBeUndefined();
|
|
594
|
+
expect(spec.paths['/items']).toBeDefined();
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should exclude specified methods', async () => {
|
|
598
|
+
const plugin = createPlugin({
|
|
599
|
+
specUrl: '/openapi.json',
|
|
600
|
+
excludeMethods: ['post'],
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
const testApp = await createTestApplication({
|
|
604
|
+
module: TestAppModule,
|
|
605
|
+
appOptions: {
|
|
606
|
+
plugins: [plugin],
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
const response = await testApp.request('/openapi.json');
|
|
611
|
+
const envelope = await response.json();
|
|
612
|
+
const spec = envelope.data;
|
|
613
|
+
|
|
614
|
+
expect(spec.paths['/items']?.post).toBeUndefined();
|
|
615
|
+
expect(spec.paths['/items']?.get).toBeDefined();
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('should exclude specified tags', async () => {
|
|
619
|
+
const plugin = createPlugin({
|
|
620
|
+
specUrl: '/openapi.json',
|
|
621
|
+
excludeTags: ['System'],
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const testApp = await createTestApplication({
|
|
625
|
+
module: TestAppModule,
|
|
626
|
+
appOptions: {
|
|
627
|
+
plugins: [plugin],
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const response = await testApp.request('/openapi.json');
|
|
632
|
+
const envelope = await response.json();
|
|
633
|
+
const spec = envelope.data;
|
|
634
|
+
|
|
635
|
+
expect(spec.paths['/healthz']).toBeUndefined();
|
|
636
|
+
expect(spec.paths['/items']).toBeDefined();
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
describe('request lifecycle', () => {
|
|
641
|
+
it('should serve spec via GET specUrl', async () => {
|
|
642
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
643
|
+
|
|
644
|
+
const testApp = await createTestApplication({
|
|
645
|
+
module: TestAppModule,
|
|
646
|
+
appOptions: {
|
|
647
|
+
plugins: [plugin],
|
|
648
|
+
},
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const response = await testApp.request('/openapi.json');
|
|
652
|
+
expect(response.status).toBe(200);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('should serve UI via GET uiRoute', async () => {
|
|
656
|
+
const plugin = createPlugin({ uiRoute: '/docs', specUrl: '/openapi.json' });
|
|
657
|
+
|
|
658
|
+
const testApp = await createTestApplication({
|
|
659
|
+
module: TestAppModule,
|
|
660
|
+
appOptions: {
|
|
661
|
+
plugins: [plugin],
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const response = await testApp.request('/docs');
|
|
666
|
+
expect(response.status).toBe(200);
|
|
667
|
+
expect(response.headers.get('content-type')).toContain('text/html');
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('should serve renderers via GET uiRoute/:renderer', async () => {
|
|
671
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
672
|
+
|
|
673
|
+
const testApp = await createTestApplication({
|
|
674
|
+
module: TestAppModule,
|
|
675
|
+
appOptions: {
|
|
676
|
+
plugins: [plugin],
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
const swaggerResponse = await testApp.request('/docs/swagger');
|
|
681
|
+
expect(swaggerResponse.status).toBe(200);
|
|
682
|
+
expect(swaggerResponse.headers.get('content-type')).toContain('text/html');
|
|
683
|
+
|
|
684
|
+
const scalarResponse = await testApp.request('/docs/scalar');
|
|
685
|
+
expect(scalarResponse.status).toBe(200);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('should return 404 for unknown renderer', async () => {
|
|
689
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
690
|
+
|
|
691
|
+
const testApp = await createTestApplication({
|
|
692
|
+
module: TestAppModule,
|
|
693
|
+
appOptions: {
|
|
694
|
+
plugins: [plugin],
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
const response = await testApp.request('/docs/nonexistent');
|
|
699
|
+
expect(response.status).toBe(404);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('should respect onRequest hook', async () => {
|
|
703
|
+
const onRequest = vi.fn();
|
|
704
|
+
const plugin = createPlugin({
|
|
705
|
+
specUrl: '/openapi.json',
|
|
706
|
+
onRequest,
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
const testApp = await createTestApplication({
|
|
710
|
+
module: TestAppModule,
|
|
711
|
+
appOptions: {
|
|
712
|
+
plugins: [plugin],
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
await testApp.request('/openapi.json');
|
|
717
|
+
expect(onRequest).toHaveBeenCalledTimes(1);
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
describe('routing prefix and version', () => {
|
|
722
|
+
it('should work with routing prefix', async () => {
|
|
723
|
+
const plugin = createPlugin({ specUrl: '/openapi.json' });
|
|
724
|
+
|
|
725
|
+
const testApp = await createTestApplication({
|
|
726
|
+
module: TestAppModule,
|
|
727
|
+
appOptions: {
|
|
728
|
+
plugins: [plugin],
|
|
729
|
+
routing: { prefix: 'api', version: 1 },
|
|
730
|
+
},
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
const response = await testApp.request('/api/v1/openapi.json');
|
|
734
|
+
expect(response.status).toBe(200);
|
|
735
|
+
|
|
736
|
+
const envelope = await response.json();
|
|
737
|
+
expect(envelope.data.paths['/api/v1/items']?.get).toBeDefined();
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
});
|