@geekmidas/constructs 0.0.2 → 0.0.3
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/package.json +4 -4
- package/src/endpoints/AmazonApiGatewayEndpointAdaptor.ts +62 -13
- package/src/endpoints/Endpoint.ts +243 -19
- package/src/endpoints/HonoEndpointAdaptor.ts +52 -16
- package/src/endpoints/TestEndpointAdaptor.ts +45 -12
- package/src/endpoints/__tests__/AmazonApiGatewayV1EndpointAdaptor.spec.ts +240 -0
- package/src/endpoints/__tests__/AmazonApiGatewayV2EndpointAdaptor.spec.ts +177 -200
- package/src/endpoints/__tests__/Endpoint.cookies.spec.ts +120 -0
- package/src/endpoints/__tests__/Endpoint.spec.ts +12 -0
- package/src/endpoints/__tests__/ResponseBuilder.spec.ts +235 -0
- package/src/endpoints/__tests__/TestEndpointAdaptor.spec.ts +348 -0
|
@@ -13,7 +13,12 @@ import {
|
|
|
13
13
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
14
14
|
import { publishConstructEvents } from '../publisher';
|
|
15
15
|
import type { HttpMethod } from '../types';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
Endpoint,
|
|
18
|
+
type EndpointSchemas,
|
|
19
|
+
ResponseBuilder,
|
|
20
|
+
type ResponseWithMetadata,
|
|
21
|
+
} from './Endpoint';
|
|
17
22
|
|
|
18
23
|
export class TestEndpointAdaptor<
|
|
19
24
|
TRoute extends string,
|
|
@@ -79,7 +84,10 @@ export class TestEndpointAdaptor<
|
|
|
79
84
|
TEventPublisher,
|
|
80
85
|
TEventPublisherServiceName
|
|
81
86
|
>,
|
|
82
|
-
): Promise<
|
|
87
|
+
): Promise<
|
|
88
|
+
| InferStandardSchema<TOutSchema>
|
|
89
|
+
| ResponseWithMetadata<InferStandardSchema<TOutSchema>>
|
|
90
|
+
> {
|
|
83
91
|
const body = await this.endpoint.parseInput((ctx as any).body, 'body');
|
|
84
92
|
const query = await this.endpoint.parseInput((ctx as any).query, 'query');
|
|
85
93
|
const params = await this.endpoint.parseInput(
|
|
@@ -88,6 +96,7 @@ export class TestEndpointAdaptor<
|
|
|
88
96
|
);
|
|
89
97
|
|
|
90
98
|
const header = Endpoint.createHeaders(ctx.headers);
|
|
99
|
+
const cookie = Endpoint.createCookies(ctx.headers.cookie);
|
|
91
100
|
const logger = this.endpoint.logger.child({
|
|
92
101
|
route: this.endpoint.route,
|
|
93
102
|
host: ctx.headers.host,
|
|
@@ -97,23 +106,47 @@ export class TestEndpointAdaptor<
|
|
|
97
106
|
logger,
|
|
98
107
|
services: ctx.services,
|
|
99
108
|
header,
|
|
109
|
+
cookie,
|
|
100
110
|
});
|
|
101
111
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
const responseBuilder = new ResponseBuilder();
|
|
113
|
+
const response = await this.endpoint.handler(
|
|
114
|
+
{
|
|
115
|
+
body,
|
|
116
|
+
query,
|
|
117
|
+
params,
|
|
118
|
+
session,
|
|
119
|
+
services: ctx.services,
|
|
120
|
+
logger,
|
|
121
|
+
header,
|
|
122
|
+
cookie,
|
|
123
|
+
} as any,
|
|
124
|
+
responseBuilder,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Check if response has metadata
|
|
128
|
+
let data = response;
|
|
129
|
+
let metadata = responseBuilder.getMetadata();
|
|
111
130
|
|
|
112
|
-
|
|
131
|
+
if (Endpoint.hasMetadata(response)) {
|
|
132
|
+
data = response.data;
|
|
133
|
+
metadata = response.metadata;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const output = await this.endpoint.parseOutput(data);
|
|
113
137
|
ctx.publisher && (await this.serviceDiscovery.register([ctx.publisher]));
|
|
114
138
|
|
|
115
139
|
await publishConstructEvents(this.endpoint, output, this.serviceDiscovery);
|
|
116
140
|
|
|
141
|
+
// Return with metadata if any was set
|
|
142
|
+
if (
|
|
143
|
+
(metadata.headers && Object.keys(metadata.headers).length > 0) ||
|
|
144
|
+
(metadata.cookies && metadata.cookies.size > 0) ||
|
|
145
|
+
metadata.status
|
|
146
|
+
) {
|
|
147
|
+
return { data: output, metadata };
|
|
148
|
+
}
|
|
149
|
+
|
|
117
150
|
return output;
|
|
118
151
|
}
|
|
119
152
|
}
|
|
@@ -924,4 +924,244 @@ describe('AmazonApiGatewayV1Endpoint', () => {
|
|
|
924
924
|
);
|
|
925
925
|
});
|
|
926
926
|
});
|
|
927
|
+
|
|
928
|
+
describe('response metadata', () => {
|
|
929
|
+
it('should set response cookies', async () => {
|
|
930
|
+
const endpoint = new Endpoint({
|
|
931
|
+
route: '/test',
|
|
932
|
+
method: 'GET',
|
|
933
|
+
fn: async (_, response) => {
|
|
934
|
+
response.cookie('session', 'abc123', {
|
|
935
|
+
httpOnly: true,
|
|
936
|
+
secure: true,
|
|
937
|
+
});
|
|
938
|
+
return { success: true };
|
|
939
|
+
},
|
|
940
|
+
input: {},
|
|
941
|
+
output: z.object({ success: z.boolean() }),
|
|
942
|
+
services: [],
|
|
943
|
+
logger: mockLogger,
|
|
944
|
+
timeout: undefined,
|
|
945
|
+
status: undefined,
|
|
946
|
+
getSession: undefined,
|
|
947
|
+
authorize: undefined,
|
|
948
|
+
description: 'Test endpoint',
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
|
|
952
|
+
const handler = adapter.handler;
|
|
953
|
+
|
|
954
|
+
const event = createMockV1Event();
|
|
955
|
+
const context = createMockContext();
|
|
956
|
+
const response = await handler(event, context);
|
|
957
|
+
|
|
958
|
+
expect(response.multiValueHeaders?.['Set-Cookie']).toEqual([
|
|
959
|
+
'session=abc123; HttpOnly; Secure',
|
|
960
|
+
]);
|
|
961
|
+
expect(response.statusCode).toBe(200);
|
|
962
|
+
expect(response.body).toBe(JSON.stringify({ success: true }));
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
it('should set custom headers', async () => {
|
|
966
|
+
const endpoint = new Endpoint({
|
|
967
|
+
route: '/test',
|
|
968
|
+
method: 'GET',
|
|
969
|
+
fn: async (_, response) => {
|
|
970
|
+
response.header('X-Custom-Header', 'custom-value');
|
|
971
|
+
response.header('X-Request-Id', '12345');
|
|
972
|
+
return { success: true };
|
|
973
|
+
},
|
|
974
|
+
input: {},
|
|
975
|
+
output: z.object({ success: z.boolean() }),
|
|
976
|
+
services: [],
|
|
977
|
+
logger: mockLogger,
|
|
978
|
+
timeout: undefined,
|
|
979
|
+
status: undefined,
|
|
980
|
+
getSession: undefined,
|
|
981
|
+
authorize: undefined,
|
|
982
|
+
description: 'Test endpoint',
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
|
|
986
|
+
const handler = adapter.handler;
|
|
987
|
+
|
|
988
|
+
const event = createMockV1Event();
|
|
989
|
+
const context = createMockContext();
|
|
990
|
+
const response = await handler(event, context);
|
|
991
|
+
|
|
992
|
+
expect(response.headers).toEqual({
|
|
993
|
+
'X-Custom-Header': 'custom-value',
|
|
994
|
+
'X-Request-Id': '12345',
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
it('should set custom status code', async () => {
|
|
999
|
+
const endpoint = new Endpoint({
|
|
1000
|
+
route: '/test',
|
|
1001
|
+
method: 'POST',
|
|
1002
|
+
fn: async (_, response) => {
|
|
1003
|
+
response.status(201);
|
|
1004
|
+
return { id: '123' };
|
|
1005
|
+
},
|
|
1006
|
+
input: {},
|
|
1007
|
+
output: z.object({ id: z.string() }),
|
|
1008
|
+
services: [],
|
|
1009
|
+
logger: mockLogger,
|
|
1010
|
+
timeout: undefined,
|
|
1011
|
+
status: undefined,
|
|
1012
|
+
getSession: undefined,
|
|
1013
|
+
authorize: undefined,
|
|
1014
|
+
description: 'Test endpoint',
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
|
|
1018
|
+
const handler = adapter.handler;
|
|
1019
|
+
|
|
1020
|
+
const event = createMockV1Event({ httpMethod: 'POST' });
|
|
1021
|
+
const context = createMockContext();
|
|
1022
|
+
const response = await handler(event, context);
|
|
1023
|
+
|
|
1024
|
+
expect(response.statusCode).toBe(201);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
it('should combine cookies, headers, and status', async () => {
|
|
1028
|
+
const endpoint = new Endpoint({
|
|
1029
|
+
route: '/test',
|
|
1030
|
+
method: 'POST',
|
|
1031
|
+
fn: async (_, response) => {
|
|
1032
|
+
response
|
|
1033
|
+
.status(201)
|
|
1034
|
+
.header('Location', '/test/123')
|
|
1035
|
+
.cookie('session', 'abc123', { httpOnly: true })
|
|
1036
|
+
.cookie('theme', 'dark');
|
|
1037
|
+
return { id: '123' };
|
|
1038
|
+
},
|
|
1039
|
+
input: {},
|
|
1040
|
+
output: z.object({ id: z.string() }),
|
|
1041
|
+
services: [],
|
|
1042
|
+
logger: mockLogger,
|
|
1043
|
+
timeout: undefined,
|
|
1044
|
+
status: undefined,
|
|
1045
|
+
getSession: undefined,
|
|
1046
|
+
authorize: undefined,
|
|
1047
|
+
description: 'Test endpoint',
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
|
|
1051
|
+
const handler = adapter.handler;
|
|
1052
|
+
|
|
1053
|
+
const event = createMockV1Event({ httpMethod: 'POST' });
|
|
1054
|
+
const context = createMockContext();
|
|
1055
|
+
const response = await handler(event, context);
|
|
1056
|
+
|
|
1057
|
+
expect(response.statusCode).toBe(201);
|
|
1058
|
+
expect(response.headers).toEqual({ Location: '/test/123' });
|
|
1059
|
+
expect(response.multiValueHeaders?.['Set-Cookie']).toEqual([
|
|
1060
|
+
'session=abc123; HttpOnly',
|
|
1061
|
+
'theme=dark',
|
|
1062
|
+
]);
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
it('should delete cookies', async () => {
|
|
1066
|
+
const endpoint = new Endpoint({
|
|
1067
|
+
route: '/test',
|
|
1068
|
+
method: 'GET',
|
|
1069
|
+
fn: async (_, response) => {
|
|
1070
|
+
response.deleteCookie('session', {
|
|
1071
|
+
path: '/',
|
|
1072
|
+
domain: '.example.com',
|
|
1073
|
+
});
|
|
1074
|
+
return { success: true };
|
|
1075
|
+
},
|
|
1076
|
+
input: {},
|
|
1077
|
+
output: z.object({ success: z.boolean() }),
|
|
1078
|
+
services: [],
|
|
1079
|
+
logger: mockLogger,
|
|
1080
|
+
timeout: undefined,
|
|
1081
|
+
status: undefined,
|
|
1082
|
+
getSession: undefined,
|
|
1083
|
+
authorize: undefined,
|
|
1084
|
+
description: 'Test endpoint',
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
|
|
1088
|
+
const handler = adapter.handler;
|
|
1089
|
+
|
|
1090
|
+
const event = createMockV1Event();
|
|
1091
|
+
const context = createMockContext();
|
|
1092
|
+
const response = await handler(event, context);
|
|
1093
|
+
|
|
1094
|
+
expect(response.multiValueHeaders?.['Set-Cookie']).toEqual([
|
|
1095
|
+
'session=; Domain=.example.com; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0',
|
|
1096
|
+
]);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
it('should use send() method with metadata', async () => {
|
|
1100
|
+
const endpoint = new Endpoint({
|
|
1101
|
+
route: '/test',
|
|
1102
|
+
method: 'GET',
|
|
1103
|
+
fn: async (_, response) => {
|
|
1104
|
+
return response
|
|
1105
|
+
.status(201)
|
|
1106
|
+
.header('X-Custom', 'value')
|
|
1107
|
+
.cookie('session', 'abc123')
|
|
1108
|
+
.send({ id: '123' });
|
|
1109
|
+
},
|
|
1110
|
+
input: {},
|
|
1111
|
+
output: z.object({ id: z.string() }),
|
|
1112
|
+
services: [],
|
|
1113
|
+
logger: mockLogger,
|
|
1114
|
+
timeout: undefined,
|
|
1115
|
+
status: undefined,
|
|
1116
|
+
getSession: undefined,
|
|
1117
|
+
authorize: undefined,
|
|
1118
|
+
description: 'Test endpoint',
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
|
|
1122
|
+
const handler = adapter.handler;
|
|
1123
|
+
|
|
1124
|
+
const event = createMockV1Event();
|
|
1125
|
+
const context = createMockContext();
|
|
1126
|
+
const response = await handler(event, context);
|
|
1127
|
+
|
|
1128
|
+
expect(response.statusCode).toBe(201);
|
|
1129
|
+
expect(response.headers).toEqual({ 'X-Custom': 'value' });
|
|
1130
|
+
expect(response.multiValueHeaders?.['Set-Cookie']).toEqual([
|
|
1131
|
+
'session=abc123',
|
|
1132
|
+
]);
|
|
1133
|
+
expect(response.body).toBe(JSON.stringify({ id: '123' }));
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
it('should return simple response without metadata when not using response builder', async () => {
|
|
1137
|
+
const endpoint = new Endpoint({
|
|
1138
|
+
route: '/test',
|
|
1139
|
+
method: 'GET',
|
|
1140
|
+
fn: async () => ({ success: true }),
|
|
1141
|
+
input: {},
|
|
1142
|
+
output: z.object({ success: z.boolean() }),
|
|
1143
|
+
services: [],
|
|
1144
|
+
logger: mockLogger,
|
|
1145
|
+
timeout: undefined,
|
|
1146
|
+
status: undefined,
|
|
1147
|
+
getSession: undefined,
|
|
1148
|
+
authorize: undefined,
|
|
1149
|
+
description: 'Test endpoint',
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, endpoint);
|
|
1153
|
+
const handler = adapter.handler;
|
|
1154
|
+
|
|
1155
|
+
const event = createMockV1Event();
|
|
1156
|
+
const context = createMockContext();
|
|
1157
|
+
const response = await handler(event, context);
|
|
1158
|
+
|
|
1159
|
+
expect(response).toEqual({
|
|
1160
|
+
statusCode: 200,
|
|
1161
|
+
body: JSON.stringify({ success: true }),
|
|
1162
|
+
});
|
|
1163
|
+
expect(response.headers).toBeUndefined();
|
|
1164
|
+
expect(response.multiValueHeaders).toBeUndefined();
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
927
1167
|
});
|