@travetto/web 6.0.0-rc.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 +734 -0
- package/__index__.ts +44 -0
- package/package.json +66 -0
- package/src/common/global.ts +30 -0
- package/src/config.ts +18 -0
- package/src/context.ts +49 -0
- package/src/decorator/common.ts +87 -0
- package/src/decorator/controller.ts +13 -0
- package/src/decorator/endpoint.ts +102 -0
- package/src/decorator/param.ts +64 -0
- package/src/interceptor/accept.ts +70 -0
- package/src/interceptor/body-parse.ts +123 -0
- package/src/interceptor/compress.ts +119 -0
- package/src/interceptor/context.ts +23 -0
- package/src/interceptor/cookies.ts +97 -0
- package/src/interceptor/cors.ts +94 -0
- package/src/interceptor/decompress.ts +91 -0
- package/src/interceptor/etag.ts +99 -0
- package/src/interceptor/logging.ts +71 -0
- package/src/interceptor/respond.ts +26 -0
- package/src/interceptor/response-cache.ts +47 -0
- package/src/interceptor/trust-proxy.ts +53 -0
- package/src/registry/controller.ts +288 -0
- package/src/registry/types.ts +229 -0
- package/src/registry/visitor.ts +52 -0
- package/src/router/base.ts +67 -0
- package/src/router/standard.ts +59 -0
- package/src/types/cookie.ts +18 -0
- package/src/types/core.ts +33 -0
- package/src/types/dispatch.ts +23 -0
- package/src/types/error.ts +10 -0
- package/src/types/filter.ts +7 -0
- package/src/types/headers.ts +108 -0
- package/src/types/interceptor.ts +54 -0
- package/src/types/message.ts +33 -0
- package/src/types/request.ts +22 -0
- package/src/types/response.ts +20 -0
- package/src/util/body.ts +220 -0
- package/src/util/common.ts +142 -0
- package/src/util/cookie.ts +145 -0
- package/src/util/endpoint.ts +277 -0
- package/src/util/mime.ts +36 -0
- package/src/util/net.ts +61 -0
- package/support/test/dispatch-util.ts +90 -0
- package/support/test/dispatcher.ts +15 -0
- package/support/test/suite/base.ts +61 -0
- package/support/test/suite/controller.ts +103 -0
- package/support/test/suite/schema.ts +275 -0
- package/support/test/suite/standard.ts +178 -0
- package/support/transformer.web.ts +207 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
2
|
+
|
|
3
|
+
import { AppError, BinaryUtil, castTo } from '@travetto/runtime';
|
|
4
|
+
import { BindUtil } from '@travetto/schema';
|
|
5
|
+
|
|
6
|
+
import { WebResponse } from '../../src/types/response.ts';
|
|
7
|
+
import { WebRequest } from '../../src/types/request.ts';
|
|
8
|
+
import { DecompressInterceptor } from '../../src/interceptor/decompress.ts';
|
|
9
|
+
import { WebBodyUtil } from '../../src/util/body.ts';
|
|
10
|
+
import { WebCommonUtil } from '../../src/util/common.ts';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Utilities for supporting custom test dispatchers
|
|
14
|
+
*/
|
|
15
|
+
export class WebTestDispatchUtil {
|
|
16
|
+
|
|
17
|
+
static async applyRequestBody(request: WebRequest): Promise<WebRequest> {
|
|
18
|
+
if (request.body !== undefined) {
|
|
19
|
+
const sample = WebBodyUtil.toBinaryMessage(request);
|
|
20
|
+
sample.headers.forEach((v, k) => request.headers.set(k, Array.isArray(v) ? v.join(',') : v));
|
|
21
|
+
request.body = WebBodyUtil.markRaw(await WebBodyUtil.toBuffer(sample.body!));
|
|
22
|
+
}
|
|
23
|
+
Object.assign(request.context, { httpQuery: BindUtil.flattenPaths(request.context.httpQuery ?? {}) });
|
|
24
|
+
return request;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static async finalizeResponseBody(response: WebResponse, decompress?: boolean): Promise<WebResponse> {
|
|
28
|
+
let result = response.body;
|
|
29
|
+
|
|
30
|
+
response.context.httpStatusCode = WebCommonUtil.getStatusCode(response);
|
|
31
|
+
|
|
32
|
+
if (decompress) {
|
|
33
|
+
if (Buffer.isBuffer(result) || BinaryUtil.isReadable(result)) {
|
|
34
|
+
const bufferResult = result = await WebBodyUtil.toBuffer(result);
|
|
35
|
+
if (bufferResult.length) {
|
|
36
|
+
try {
|
|
37
|
+
result = await DecompressInterceptor.decompress(
|
|
38
|
+
response.headers,
|
|
39
|
+
bufferResult,
|
|
40
|
+
{ applies: true, supportedEncodings: ['br', 'deflate', 'gzip', 'identity'] }
|
|
41
|
+
);
|
|
42
|
+
} catch { }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const text = Buffer.isBuffer(result) ? result.toString('utf8') : (typeof result === 'string' ? result : undefined);
|
|
48
|
+
|
|
49
|
+
if (text) {
|
|
50
|
+
switch (response.headers.get('Content-Type')) {
|
|
51
|
+
case 'application/json': result = JSON.parse(castTo(text)); break;
|
|
52
|
+
case 'text/plain': result = text; break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (response.context.httpStatusCode && response.context.httpStatusCode >= 400) {
|
|
57
|
+
result = WebCommonUtil.catchResponse(AppError.fromJSON(result) ?? result).body;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
response.body = result;
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async toFetchRequestInit(request: WebRequest): Promise<{ init: RequestInit, path: string }> {
|
|
65
|
+
const { context: { httpQuery: query, httpMethod: method, path }, headers } = request;
|
|
66
|
+
|
|
67
|
+
let q = '';
|
|
68
|
+
if (query && Object.keys(query).length) {
|
|
69
|
+
const pairs = Object.fromEntries(Object.entries(query).map(([k, v]) => [k, v === null || v === undefined ? '' : `${v}`] as const));
|
|
70
|
+
q = `?${new URLSearchParams(pairs).toString()}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const finalPath = `${path}${q}`;
|
|
74
|
+
|
|
75
|
+
const body: RequestInit['body'] =
|
|
76
|
+
WebBodyUtil.isRaw(request.body) ?
|
|
77
|
+
Buffer.isBuffer(request.body) ? request.body : await toBuffer(request.body) :
|
|
78
|
+
castTo(request.body);
|
|
79
|
+
|
|
80
|
+
return { path: finalPath, init: { headers, method, body } };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static async fromFetchResponse(response: Response): Promise<WebResponse> {
|
|
84
|
+
return new WebResponse({
|
|
85
|
+
body: Buffer.from(await response.arrayBuffer()),
|
|
86
|
+
context: { httpStatusCode: response.status },
|
|
87
|
+
headers: response.headers
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Injectable } from '@travetto/di';
|
|
2
|
+
|
|
3
|
+
import type { WebFilterContext } from '../../src/types/filter.ts';
|
|
4
|
+
import { WebResponse } from '../../src/types/response.ts';
|
|
5
|
+
import { StandardWebRouter } from '../../src/router/standard.ts';
|
|
6
|
+
import { WebTestDispatchUtil } from './dispatch-util.ts';
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class LocalRequestDispatcher extends StandardWebRouter {
|
|
10
|
+
async dispatch({ request }: WebFilterContext): Promise<WebResponse> {
|
|
11
|
+
const resolved = await WebTestDispatchUtil.applyRequestBody(request);
|
|
12
|
+
const res = await super.dispatch({ request: resolved });
|
|
13
|
+
return WebTestDispatchUtil.finalizeResponseBody(res, true);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { RootRegistry } from '@travetto/registry';
|
|
2
|
+
import { castTo, Class } from '@travetto/runtime';
|
|
3
|
+
import { AfterAll, BeforeAll } from '@travetto/test';
|
|
4
|
+
import { DependencyRegistry, Injectable } from '@travetto/di';
|
|
5
|
+
import { ConfigSource, ConfigSpec } from '@travetto/config';
|
|
6
|
+
|
|
7
|
+
import { WebDispatcher } from '../../../src/types/dispatch.ts';
|
|
8
|
+
import { WebRequest, WebRequestContext } from '../../../src/types/request.ts';
|
|
9
|
+
import { WebResponse } from '../../../src/types/response.ts';
|
|
10
|
+
import { WebMessageInit } from '../../../src/types/message.ts';
|
|
11
|
+
|
|
12
|
+
@Injectable()
|
|
13
|
+
export class WebTestConfig implements ConfigSource {
|
|
14
|
+
async get(): Promise<ConfigSpec> {
|
|
15
|
+
return {
|
|
16
|
+
data: {
|
|
17
|
+
web: {
|
|
18
|
+
cookie: { secure: false },
|
|
19
|
+
trustProxy: { ips: ['*'] },
|
|
20
|
+
http: {
|
|
21
|
+
ssl: { active: false },
|
|
22
|
+
port: -1,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
source: 'custom://test/web',
|
|
27
|
+
priority: 10000
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base Web Suite
|
|
34
|
+
*/
|
|
35
|
+
export abstract class BaseWebSuite {
|
|
36
|
+
|
|
37
|
+
#cleanup?: () => void;
|
|
38
|
+
#dispatcher: WebDispatcher;
|
|
39
|
+
|
|
40
|
+
serve?(): Promise<() => void>;
|
|
41
|
+
dispatcherType: Class<WebDispatcher>;
|
|
42
|
+
|
|
43
|
+
@BeforeAll()
|
|
44
|
+
async initServer(): Promise<void> {
|
|
45
|
+
await RootRegistry.init();
|
|
46
|
+
this.#cleanup = await this.serve?.();
|
|
47
|
+
this.#dispatcher = await DependencyRegistry.getInstance(this.dispatcherType);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@AfterAll()
|
|
51
|
+
async destroySever(): Promise<void> {
|
|
52
|
+
await this.#cleanup?.();
|
|
53
|
+
this.#cleanup = undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async request<T>(cfg: WebMessageInit<unknown, WebRequestContext>, throwOnError: boolean = true): Promise<WebResponse<T>> {
|
|
57
|
+
const response = await this.#dispatcher.dispatch({ request: new WebRequest(cfg) });
|
|
58
|
+
if (throwOnError && response.context.httpStatusCode && response.context.httpStatusCode >= 400) { throw response.body; }
|
|
59
|
+
return castTo(response);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
|
|
3
|
+
import { AppError, castTo } from '@travetto/runtime';
|
|
4
|
+
|
|
5
|
+
import { Controller } from '../../../src/decorator/controller.ts';
|
|
6
|
+
import { Get, Post, Put, Delete, Patch } from '../../../src/decorator/endpoint.ts';
|
|
7
|
+
import { ContextParam, PathParam, QueryParam } from '../../../src/decorator/param.ts';
|
|
8
|
+
import { Produces, SetHeaders } from '../../../src/decorator/common.ts';
|
|
9
|
+
|
|
10
|
+
import { WebRequest } from '../../../src/types/request.ts';
|
|
11
|
+
import { WebResponse } from '../../../src/types/response.ts';
|
|
12
|
+
import { CookieJar } from '../../../src/util/cookie.ts';
|
|
13
|
+
|
|
14
|
+
@Controller('/test')
|
|
15
|
+
export class TestController {
|
|
16
|
+
|
|
17
|
+
@ContextParam()
|
|
18
|
+
request: WebRequest;
|
|
19
|
+
|
|
20
|
+
@ContextParam()
|
|
21
|
+
cookies: CookieJar;
|
|
22
|
+
|
|
23
|
+
@Get('/json')
|
|
24
|
+
getJSON() {
|
|
25
|
+
return { json: true };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Get('/json/large/:size')
|
|
29
|
+
getJSONLarge(size = 20000) {
|
|
30
|
+
return { json: '0123456789'.repeat(size / 10) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Post('/param/:param')
|
|
34
|
+
withParam(@PathParam() param: string) {
|
|
35
|
+
return { param };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Put('/query')
|
|
39
|
+
withQuery(@QueryParam() age: number) {
|
|
40
|
+
return { query: age };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Put('/body')
|
|
44
|
+
withBody() {
|
|
45
|
+
return { body: castTo<{ age: number }>(this.request.body).age };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Delete('/cookie')
|
|
49
|
+
withCookie() {
|
|
50
|
+
this.cookies.set({ name: 'flavor', value: 'oreo' });
|
|
51
|
+
return new WebResponse({ body: { cookie: this.cookies.get('orange') } });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Patch('/regexp/super-:special-party')
|
|
55
|
+
withRegexp(@PathParam() special: string) {
|
|
56
|
+
return { path: special };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Get('/stream')
|
|
60
|
+
@SetHeaders({ 'Content-Type': 'text/plain' })
|
|
61
|
+
getStream() {
|
|
62
|
+
return Readable.from(Buffer.from('hello'));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Get('/buffer')
|
|
66
|
+
@Produces('text/plain')
|
|
67
|
+
getBuffer() {
|
|
68
|
+
return Buffer.from('hello');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@Get('/renderable')
|
|
72
|
+
@Produces('text/plain')
|
|
73
|
+
getRenderable(): WebResponse<string> {
|
|
74
|
+
return new WebResponse({ body: 'hello' });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Get('/fullUrl')
|
|
78
|
+
getFullUrl() {
|
|
79
|
+
return {
|
|
80
|
+
path: this.request.context.path
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Get('/headerFirst')
|
|
85
|
+
getHeaderFirst() {
|
|
86
|
+
return { header: this.request.headers.get('Age')?.split(',')?.[0] };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Get('/fun/*')
|
|
90
|
+
getFun() {
|
|
91
|
+
return { path: this.request.context.path.split('fun/')[1] };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@Get('/ip')
|
|
95
|
+
getIp() {
|
|
96
|
+
return { ip: this.request.context.connection?.ip };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Post('/ip')
|
|
100
|
+
notFound() {
|
|
101
|
+
throw new AppError('Uh-oh', { category: 'general' });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
|
|
3
|
+
import { Suite, Test } from '@travetto/test';
|
|
4
|
+
import { Schema, SchemaRegistry, ValidationResultError, Validator } from '@travetto/schema';
|
|
5
|
+
import { Controller, Post, Get, ControllerRegistry, WebResponse, PathParam, QueryParam, HttpMethod } from '@travetto/web';
|
|
6
|
+
|
|
7
|
+
import { BaseWebSuite } from './base.ts';
|
|
8
|
+
|
|
9
|
+
interface UserShape {
|
|
10
|
+
id: number | undefined;
|
|
11
|
+
age: number;
|
|
12
|
+
name: string;
|
|
13
|
+
active: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Schema()
|
|
17
|
+
class SimpleUser {
|
|
18
|
+
id: number;
|
|
19
|
+
age: number;
|
|
20
|
+
name: string;
|
|
21
|
+
active: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Validator(user => {
|
|
25
|
+
if (user && user.age === 300) {
|
|
26
|
+
return { kind: 'value', message: 'Age cannot be 300', path: 'age' };
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
@Schema()
|
|
30
|
+
class User {
|
|
31
|
+
id: number = -1;
|
|
32
|
+
name: string;
|
|
33
|
+
age: number;
|
|
34
|
+
active: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function getUser(x: number) {
|
|
38
|
+
return User.from({});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Controller('/test/schema')
|
|
42
|
+
class SchemaAPI {
|
|
43
|
+
@Post('/user')
|
|
44
|
+
async saveUser(user: User) {
|
|
45
|
+
return user;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Get('/user')
|
|
49
|
+
async queryUser(user: User) {
|
|
50
|
+
return user;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Get('/interface')
|
|
54
|
+
async ifUser(user: UserShape) {
|
|
55
|
+
return user;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Get('/interface-prefix')
|
|
59
|
+
async ifUserPrefix(user: User, @QueryParam({ prefix: 'user2' }) user3: User) {
|
|
60
|
+
return user;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Get('/void')
|
|
64
|
+
async voidUser() {
|
|
65
|
+
return Promise.resolve((() => { })());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Get('/voidAll')
|
|
69
|
+
async voidAllUser() {
|
|
70
|
+
return Promise.resolve([1, 2, 3].map(() => { }));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Get('/users')
|
|
74
|
+
async allUsers() {
|
|
75
|
+
return Promise.all([1, 2, 3,].map(x => getUser(x)));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Get('/allShapes')
|
|
79
|
+
async allShape() {
|
|
80
|
+
return Promise.all([1, 2, 3,].map(async x => ({ key: x, count: 5 })));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@Get('/classShape/:shape')
|
|
84
|
+
async classShape(@PathParam('shape') sh: string) {
|
|
85
|
+
return new SimpleUser();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Get('/renderable/:age')
|
|
89
|
+
async renderable(age: number) {
|
|
90
|
+
return WebResponse.redirect('google.com');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Get('/customSerialize')
|
|
94
|
+
async customSerialize() {
|
|
95
|
+
return new WebResponse({ body: new User() });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @returns {Promise<User>}
|
|
100
|
+
*/
|
|
101
|
+
@Get('/customSerialize2')
|
|
102
|
+
async customSerialize2() {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getEndpoint(path: string, method: HttpMethod) {
|
|
107
|
+
return ControllerRegistry.get(SchemaAPI)
|
|
108
|
+
.endpoints.find(x => x.path === path && x.httpMethod === method)!;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@Suite()
|
|
112
|
+
export abstract class SchemaWebServerSuite extends BaseWebSuite {
|
|
113
|
+
|
|
114
|
+
@Test()
|
|
115
|
+
async verifyBody() {
|
|
116
|
+
const user = { id: '0', name: 'bob', age: '20', active: 'false' };
|
|
117
|
+
|
|
118
|
+
const response1 = await this.request<UserShape>({ context: { httpMethod: 'POST', path: '/test/schema/user' }, body: user });
|
|
119
|
+
|
|
120
|
+
assert(response1.body?.name === user.name);
|
|
121
|
+
|
|
122
|
+
const response2 = await this.request<ValidationResultError>({ context: { httpMethod: 'POST', path: '/test/schema/user' }, body: { id: 'orange' } }, false);
|
|
123
|
+
|
|
124
|
+
assert(response2.context.httpStatusCode === 400);
|
|
125
|
+
assert(/Validation errors have occurred/.test(response2.body?.message ?? ''));
|
|
126
|
+
assert(response2.body?.details.errors[0].path === 'id');
|
|
127
|
+
|
|
128
|
+
const response3 = await this.request<ValidationResultError>({ context: { httpMethod: 'POST', path: '/test/schema/user' }, body: { id: 0, name: 'bob', age: 'a' } }, false);
|
|
129
|
+
|
|
130
|
+
assert(response3.context.httpStatusCode === 400);
|
|
131
|
+
assert(/Validation errors have occurred/.test(response3.body?.message ?? ''));
|
|
132
|
+
assert(response3.body?.details.errors[0].path === 'age');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@Test()
|
|
136
|
+
async verifyQuery() {
|
|
137
|
+
const user = { id: '0', name: 'bob', age: '20', active: 'false' };
|
|
138
|
+
|
|
139
|
+
const response1 = await this.request<UserShape>({ context: { httpMethod: 'GET', path: '/test/schema/user', httpQuery: user } });
|
|
140
|
+
|
|
141
|
+
assert(response1.body?.name === user.name);
|
|
142
|
+
|
|
143
|
+
const response2 = await this.request<ValidationResultError>({ context: { httpMethod: 'GET', path: '/test/schema/user', httpQuery: { id: 'orange' } } }, false);
|
|
144
|
+
|
|
145
|
+
assert(response2.context.httpStatusCode === 400);
|
|
146
|
+
assert(/Validation errors have occurred/.test(response2.body?.message ?? ''));
|
|
147
|
+
assert(response2.body?.details.errors[0].path === 'id');
|
|
148
|
+
|
|
149
|
+
const response3 = await this.request<ValidationResultError>({
|
|
150
|
+
context: {
|
|
151
|
+
httpMethod: 'GET',
|
|
152
|
+
path: '/test/schema/user',
|
|
153
|
+
httpQuery: { id: '0', name: 'bob', age: 'a' }
|
|
154
|
+
}
|
|
155
|
+
}, false);
|
|
156
|
+
|
|
157
|
+
assert(response3.context.httpStatusCode === 400);
|
|
158
|
+
assert(/Validation errors have occurred/.test(response3.body?.message ?? ''));
|
|
159
|
+
assert(response3.body?.details.errors[0].path === 'age');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Test()
|
|
163
|
+
async verifyInterface() {
|
|
164
|
+
const user = { id: '0', name: 'bob', age: '20', active: 'false' };
|
|
165
|
+
|
|
166
|
+
const response1 = await this.request<UserShape>({ context: { httpMethod: 'GET', path: '/test/schema/interface', httpQuery: user } });
|
|
167
|
+
|
|
168
|
+
assert(response1.body?.name === user.name);
|
|
169
|
+
assert(response1.body?.age === 20);
|
|
170
|
+
|
|
171
|
+
const response2 = await this.request<ValidationResultError>({ context: { httpMethod: 'GET', path: '/test/schema/interface', httpQuery: { id: 'orange' } } }, false);
|
|
172
|
+
|
|
173
|
+
assert(response2.context.httpStatusCode === 400);
|
|
174
|
+
assert(/Validation errors have occurred/.test(response2.body?.message ?? ''));
|
|
175
|
+
assert(response2.body?.details.errors[0].path === 'id');
|
|
176
|
+
|
|
177
|
+
const response3 = await this.request<ValidationResultError>({
|
|
178
|
+
context: {
|
|
179
|
+
httpMethod: 'GET',
|
|
180
|
+
path: '/test/schema/interface',
|
|
181
|
+
httpQuery: { id: '0', name: 'bob', age: 'a' }
|
|
182
|
+
}
|
|
183
|
+
}, false);
|
|
184
|
+
|
|
185
|
+
assert(response3.context.httpStatusCode === 400);
|
|
186
|
+
assert(/Validation errors have occurred/.test(response3.body?.message ?? ''));
|
|
187
|
+
assert(response3.body?.details.errors[0].path === 'age');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@Test()
|
|
191
|
+
async verifyPrefix() {
|
|
192
|
+
const user = { id: '0', name: 'bob', age: '20', active: 'false' };
|
|
193
|
+
|
|
194
|
+
const response1 = await this.request<ValidationResultError>({ context: { httpMethod: 'GET', path: '/test/schema/interface-prefix', httpQuery: user, } }, false);
|
|
195
|
+
|
|
196
|
+
assert(response1.context.httpStatusCode === 400);
|
|
197
|
+
assert(/Validation errors have occurred/.test(response1.body?.message ?? ''));
|
|
198
|
+
console.error(response1.body);
|
|
199
|
+
assert(response1.body?.details.errors[0].path.startsWith('user2'));
|
|
200
|
+
|
|
201
|
+
const response2 = await this.request<ValidationResultError>({ context: { httpMethod: 'GET', path: '/test/schema/interface-prefix', httpQuery: { user3: user } } }, false);
|
|
202
|
+
|
|
203
|
+
assert(response2.context.httpStatusCode === 400);
|
|
204
|
+
assert(/Validation errors have occurred/.test(response2.body?.message ?? ''));
|
|
205
|
+
assert(response2.body?.details.errors[0].path);
|
|
206
|
+
assert(!response2.body?.details.errors[0].path.startsWith('user'));
|
|
207
|
+
|
|
208
|
+
const response3 = await this.request<User>({ context: { httpMethod: 'GET', path: '/test/schema/interface-prefix', httpQuery: { ...user, user2: user } } });
|
|
209
|
+
|
|
210
|
+
assert(response3.context.httpStatusCode === 200);
|
|
211
|
+
assert(response3.body?.name === user.name);
|
|
212
|
+
assert(response3.body?.age === 20);
|
|
213
|
+
|
|
214
|
+
const response4 = await this.request<ValidationResultError>({
|
|
215
|
+
context: {
|
|
216
|
+
httpMethod: 'GET', path: '/test/schema/interface-prefix',
|
|
217
|
+
httpQuery: { ...user, user2: { ...user, age: '300' } }
|
|
218
|
+
}
|
|
219
|
+
}, false);
|
|
220
|
+
|
|
221
|
+
assert(response4.context.httpStatusCode === 400);
|
|
222
|
+
assert(/Validation errors have occurred/.test(response4.body?.message ?? ''));
|
|
223
|
+
assert(response4.body?.details.errors[0].path);
|
|
224
|
+
assert(!response4.body?.details.errors[0].path.startsWith('user2.age'));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@Test()
|
|
228
|
+
async verifyVoid() {
|
|
229
|
+
const ep = getEndpoint('/void', 'GET');
|
|
230
|
+
assert(ep.responseType === undefined);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@Test()
|
|
234
|
+
async verifyVoidAll() {
|
|
235
|
+
const ep = getEndpoint('/voidAll', 'GET');
|
|
236
|
+
assert(ep.responseType === undefined);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@Test()
|
|
240
|
+
async verifyList() {
|
|
241
|
+
const ep = getEndpoint('/users', 'GET');
|
|
242
|
+
assert(ep.responseType?.type === User);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@Test()
|
|
246
|
+
async verifyShapeAll() {
|
|
247
|
+
const ep = getEndpoint('/allShapes', 'GET');
|
|
248
|
+
console.log(`${ep.responseType}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@Test()
|
|
252
|
+
async verifyShapeClass() {
|
|
253
|
+
const ep = getEndpoint('/classShape/:shape', 'GET');
|
|
254
|
+
assert(ep.responseType);
|
|
255
|
+
assert(SchemaRegistry.has(ep.responseType!.type));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@Test()
|
|
259
|
+
async verifyRenderable() {
|
|
260
|
+
const ep = getEndpoint('/renderable/:age', 'GET');
|
|
261
|
+
assert(ep.responseType?.type === undefined);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@Test()
|
|
265
|
+
async verifyCustomSerializeable() {
|
|
266
|
+
const ep = getEndpoint('/customSerialize', 'GET');
|
|
267
|
+
assert(ep.responseType?.type === User);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@Test()
|
|
271
|
+
async verifyCustomSerializeable2() {
|
|
272
|
+
const ep = getEndpoint('/customSerialize2', 'GET');
|
|
273
|
+
assert(ep.responseType?.type === User);
|
|
274
|
+
}
|
|
275
|
+
}
|