@dbos-inc/koa-serve 2.11.6-preview.gcb74958171
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 +1 -0
- package/dist/src/dboshttp.d.ts +76 -0
- package/dist/src/dboshttp.d.ts.map +1 -0
- package/dist/src/dboshttp.js +122 -0
- package/dist/src/dboshttp.js.map +1 -0
- package/dist/src/dboskoa.d.ts +54 -0
- package/dist/src/dboskoa.d.ts.map +1 -0
- package/dist/src/dboskoa.js +333 -0
- package/dist/src/dboskoa.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +12 -0
- package/dist/src/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/jest.config.js +8 -0
- package/package.json +43 -0
- package/src/dboshttp.ts +185 -0
- package/src/dboskoa.ts +415 -0
- package/src/index.ts +14 -0
- package/tests/argsource.test.ts +153 -0
- package/tests/auth.test.ts +208 -0
- package/tests/basic.test.ts +73 -0
- package/tests/cors.test.ts +292 -0
- package/tests/endpoints.test.ts +517 -0
- package/tests/validation.test.ts +539 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
2
|
+
import Koa from 'koa';
|
|
3
|
+
import Router from '@koa/router';
|
|
4
|
+
|
|
5
|
+
import { DBOS, DBOSResponseError, Error as DBOSErrors, DefaultArgRequired, StatusString } from '@dbos-inc/dbos-sdk';
|
|
6
|
+
|
|
7
|
+
import { DBOSKoa, DBOSKoaAuthContext, RequestIDHeader, WorkflowIDHeader } from '../src';
|
|
8
|
+
|
|
9
|
+
import request from 'supertest';
|
|
10
|
+
|
|
11
|
+
const dhttp = new DBOSKoa();
|
|
12
|
+
|
|
13
|
+
interface TestKvTable {
|
|
14
|
+
id?: number;
|
|
15
|
+
value?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
import { randomUUID } from 'node:crypto';
|
|
19
|
+
import { IncomingMessage } from 'http';
|
|
20
|
+
import { bodyParser } from '@koa/bodyparser';
|
|
21
|
+
|
|
22
|
+
// copied from https://github.com/uuidjs/uuid project
|
|
23
|
+
function uuidValidate(uuid: string) {
|
|
24
|
+
const regex =
|
|
25
|
+
/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
|
|
26
|
+
return regex.test(uuid);
|
|
27
|
+
}
|
|
28
|
+
describe('httpserver-tests', () => {
|
|
29
|
+
let app: Koa;
|
|
30
|
+
let appRouter: Router;
|
|
31
|
+
|
|
32
|
+
const testTableName = 'dbos_test_kv';
|
|
33
|
+
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
DBOS.setConfig({
|
|
36
|
+
name: 'dbos-koa-test',
|
|
37
|
+
userDbclient: 'pg-node',
|
|
38
|
+
});
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
DBOS.registerLifecycleCallback(dhttp);
|
|
44
|
+
const _classes = [TestEndpoints];
|
|
45
|
+
await DBOS.launch();
|
|
46
|
+
DBOS.setUpHandlerCallback();
|
|
47
|
+
await DBOS.queryUserDB(`DROP TABLE IF EXISTS ${testTableName};`);
|
|
48
|
+
await DBOS.queryUserDB(`CREATE TABLE IF NOT EXISTS ${testTableName} (id INT PRIMARY KEY, value TEXT);`);
|
|
49
|
+
app = new Koa();
|
|
50
|
+
appRouter = new Router();
|
|
51
|
+
dhttp.registerWithApp(app, appRouter);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(async () => {
|
|
55
|
+
await DBOS.shutdown();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('get-hello', async () => {
|
|
59
|
+
const response = await request(app.callback()).get('/hello');
|
|
60
|
+
expect(response.statusCode).toBe(200);
|
|
61
|
+
expect(response.body.message).toBe('hello!');
|
|
62
|
+
const requestID: string = response.headers[RequestIDHeader.toLowerCase()];
|
|
63
|
+
// Expect uuidValidate to be true
|
|
64
|
+
expect(uuidValidate(requestID)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('get-url', async () => {
|
|
68
|
+
const requestID = 'my-request-id';
|
|
69
|
+
const response = await request(app.callback()).get('/hello/alice').set(RequestIDHeader, requestID);
|
|
70
|
+
expect(response.statusCode).toBe(301);
|
|
71
|
+
expect(response.text).toBe('wow alice');
|
|
72
|
+
expect(response.headers[RequestIDHeader.toLowerCase()]).toBe(requestID);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('get-query', async () => {
|
|
76
|
+
const response = await request(app.callback()).get('/query?name=alice');
|
|
77
|
+
expect(response.statusCode).toBe(200);
|
|
78
|
+
expect(response.text).toBe('hello alice');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('get-querybody', async () => {
|
|
82
|
+
const response = await request(app.callback()).get('/querybody').send({ name: 'alice' });
|
|
83
|
+
expect(response.statusCode).toBe(200);
|
|
84
|
+
expect(response.text).toBe('hello alice');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('delete-query', async () => {
|
|
88
|
+
const response = await request(app.callback()).delete('/testdeletequery?name=alice');
|
|
89
|
+
expect(response.statusCode).toBe(200);
|
|
90
|
+
expect(response.text).toBe('hello alice');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('delete-url', async () => {
|
|
94
|
+
const response = await request(app.callback()).delete('/testdeleteurl/alice');
|
|
95
|
+
expect(response.statusCode).toBe(200);
|
|
96
|
+
expect(response.text).toBe('hello alice');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('delete-body', async () => {
|
|
100
|
+
const response = await request(app.callback()).delete('/testdeletebody').send({ name: 'alice' });
|
|
101
|
+
expect(response.statusCode).toBe(200);
|
|
102
|
+
expect(response.text).toBe('hello alice');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('post-test', async () => {
|
|
106
|
+
const response = await request(app.callback()).post('/testpost').send({ name: 'alice' });
|
|
107
|
+
expect(response.statusCode).toBe(200);
|
|
108
|
+
expect(response.text).toBe('hello alice');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('post-test-custom-body', async () => {
|
|
112
|
+
let response = await request(app.callback())
|
|
113
|
+
.post('/testpost')
|
|
114
|
+
.set('Content-Type', 'application/custom-content-type')
|
|
115
|
+
.send(JSON.stringify({ name: 'alice' }));
|
|
116
|
+
expect(response.statusCode).toBe(200);
|
|
117
|
+
expect(response.text).toBe('hello alice');
|
|
118
|
+
response = await request(app.callback())
|
|
119
|
+
.post('/testpost')
|
|
120
|
+
.set('Content-Type', 'application/rejected-custom-content-type')
|
|
121
|
+
.send(JSON.stringify({ name: 'alice' }));
|
|
122
|
+
expect(response.statusCode).toBe(400);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('put-test', async () => {
|
|
126
|
+
const response = await request(app.callback()).put('/testput').send({ name: 'alice' });
|
|
127
|
+
expect(response.statusCode).toBe(200);
|
|
128
|
+
expect(response.text).toBe('hello alice');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('put-test-custom-body', async () => {
|
|
132
|
+
let response = await request(app.callback())
|
|
133
|
+
.put('/testput')
|
|
134
|
+
.set('Content-Type', 'application/custom-content-type')
|
|
135
|
+
.send(JSON.stringify({ name: 'alice' }));
|
|
136
|
+
expect(response.statusCode).toBe(200);
|
|
137
|
+
expect(response.text).toBe('hello alice');
|
|
138
|
+
response = await request(app.callback())
|
|
139
|
+
.put('/testput')
|
|
140
|
+
.set('Content-Type', 'application/rejected-custom-content-type')
|
|
141
|
+
.send(JSON.stringify({ name: 'alice' }));
|
|
142
|
+
expect(response.statusCode).toBe(400);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('patch-test', async () => {
|
|
146
|
+
const response = await request(app.callback()).patch('/testpatch').send({ name: 'alice' });
|
|
147
|
+
expect(response.statusCode).toBe(200);
|
|
148
|
+
expect(response.text).toBe('hello alice');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('patch-test-custom-body', async () => {
|
|
152
|
+
let response = await request(app.callback())
|
|
153
|
+
.patch('/testpatch')
|
|
154
|
+
.set('Content-Type', 'application/custom-content-type')
|
|
155
|
+
.send(JSON.stringify({ name: 'alice' }));
|
|
156
|
+
expect(response.statusCode).toBe(200);
|
|
157
|
+
expect(response.text).toBe('hello alice');
|
|
158
|
+
response = await request(app.callback())
|
|
159
|
+
.patch('/testpatch')
|
|
160
|
+
.set('Content-Type', 'application/rejected-custom-content-type')
|
|
161
|
+
.send(JSON.stringify({ name: 'alice' }));
|
|
162
|
+
expect(response.statusCode).toBe(400);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('endpoint-transaction', async () => {
|
|
166
|
+
const response = await request(app.callback()).post('/transaction/alice');
|
|
167
|
+
expect(response.statusCode).toBe(200);
|
|
168
|
+
expect(response.text).toBe('hello 1');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('endpoint-step', async () => {
|
|
172
|
+
const response = await request(app.callback()).get('/step/alice');
|
|
173
|
+
expect(response.statusCode).toBe(200);
|
|
174
|
+
expect(response.text).toBe('alice');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('endpoint-workflow', async () => {
|
|
178
|
+
const response = await request(app.callback()).post('/workflow?name=alice');
|
|
179
|
+
expect(response.statusCode).toBe(200);
|
|
180
|
+
expect(response.text).toBe('hello 1');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('endpoint-error', async () => {
|
|
184
|
+
const response = await request(app.callback()).post('/error').send({ name: 'alice' });
|
|
185
|
+
expect(response.statusCode).toBe(500);
|
|
186
|
+
expect(response.body.details.code).toBe('23505'); // Should be the expected error.
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('endpoint-handler', async () => {
|
|
190
|
+
const response = await request(app.callback()).get('/handler/alice');
|
|
191
|
+
expect(response.statusCode).toBe(200);
|
|
192
|
+
expect(response.text).toBe('hello 1');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('endpoint-testStartWorkflow', async () => {
|
|
196
|
+
const response = await request(app.callback()).get('/testStartWorkflow/alice');
|
|
197
|
+
expect(response.statusCode).toBe(200);
|
|
198
|
+
expect(response.text).toBe('hello 1');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('endpoint-testInvokeWorkflow', async () => {
|
|
202
|
+
const response = await request(app.callback()).get('/testInvokeWorkflow/alice');
|
|
203
|
+
expect(response.statusCode).toBe(200);
|
|
204
|
+
expect(response.text).toBe('hello 1');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// This feels unclean, but supertest doesn't expose the error message the people we want. See:
|
|
208
|
+
// https://github.com/ladjs/supertest/issues/95
|
|
209
|
+
interface Res {
|
|
210
|
+
res: IncomingMessage;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
test('response-error', async () => {
|
|
214
|
+
const response = await request(app.callback()).get('/dbos-error');
|
|
215
|
+
expect(response.statusCode).toBe(503);
|
|
216
|
+
expect((response as unknown as Res).res.statusMessage).toBe('customize error');
|
|
217
|
+
expect(response.body.message).toBe('customize error');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('datavalidation-error', async () => {
|
|
221
|
+
const response = await request(app.callback()).get('/query');
|
|
222
|
+
expect(response.statusCode).toBe(400);
|
|
223
|
+
expect(response.body.details.dbosErrorCode).toBe(9);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('dbos-redirect', async () => {
|
|
227
|
+
const response = await request(app.callback()).get('/redirect');
|
|
228
|
+
expect(response.statusCode).toBe(302);
|
|
229
|
+
expect(response.headers.location).toBe('/redirect-dbos');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('request-is-persisted', async () => {
|
|
233
|
+
const workflowID = randomUUID();
|
|
234
|
+
const response = await request(app.callback()).get('/check-url').set({ 'dbos-idempotency-key': workflowID });
|
|
235
|
+
expect(response.statusCode).toBe(200);
|
|
236
|
+
expect(response.text).toBe('/check-url');
|
|
237
|
+
|
|
238
|
+
// Retrieve the workflow with WFID.
|
|
239
|
+
const retrievedHandle = DBOS.retrieveWorkflow(workflowID);
|
|
240
|
+
expect(retrievedHandle).not.toBeNull();
|
|
241
|
+
await expect(retrievedHandle.getResult()).resolves.toBe('/check-url');
|
|
242
|
+
await expect(retrievedHandle.getStatus()).resolves.toMatchObject({
|
|
243
|
+
status: StatusString.SUCCESS,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Start another WF based on that...
|
|
247
|
+
const wfh = await DBOS.forkWorkflow(workflowID, 0);
|
|
248
|
+
await expect(wfh.getResult()).resolves.toBe(`/check-url`);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('not-authenticated', async () => {
|
|
252
|
+
const response = await request(app.callback()).get('/requireduser?name=alice');
|
|
253
|
+
expect(response.statusCode).toBe(401);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('not-you', async () => {
|
|
257
|
+
const response = await request(app.callback()).get('/requireduser?name=alice&userid=go_away');
|
|
258
|
+
expect(response.statusCode).toBe(401);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('not-authorized', async () => {
|
|
262
|
+
const response = await request(app.callback()).get('/requireduser?name=alice&userid=bob');
|
|
263
|
+
expect(response.statusCode).toBe(403);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('authorized', async () => {
|
|
267
|
+
const response = await request(app.callback()).get('/requireduser?name=alice&userid=a_real_user');
|
|
268
|
+
expect(response.statusCode).toBe(200);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('not-authenticated2', async () => {
|
|
272
|
+
const response = await request(app.callback()).get('/requireduser2?name=alice');
|
|
273
|
+
expect(response.statusCode).toBe(401);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('not-you2', async () => {
|
|
277
|
+
const response = await request(app.callback()).get('/requireduser2?name=alice&userid=go_away');
|
|
278
|
+
expect(response.statusCode).toBe(401);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('not-authorized2', async () => {
|
|
282
|
+
const response = await request(app.callback()).get('/requireduser2?name=alice&userid=bob');
|
|
283
|
+
expect(response.statusCode).toBe(403);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('authorized2', async () => {
|
|
287
|
+
const response = await request(app.callback()).get('/requireduser2?name=alice&userid=a_real_user');
|
|
288
|
+
expect(response.statusCode).toBe(200);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('test-workflowID-header', async () => {
|
|
292
|
+
const workflowID = randomUUID();
|
|
293
|
+
const response = await request(app.callback())
|
|
294
|
+
.post('/workflow?name=bob')
|
|
295
|
+
.set({ 'dbos-idempotency-key': workflowID });
|
|
296
|
+
expect(response.statusCode).toBe(200);
|
|
297
|
+
expect(response.text).toBe('hello 1');
|
|
298
|
+
|
|
299
|
+
// Retrieve the workflow with WFID.
|
|
300
|
+
const retrievedHandle = DBOS.retrieveWorkflow(workflowID);
|
|
301
|
+
expect(retrievedHandle).not.toBeNull();
|
|
302
|
+
await expect(retrievedHandle.getResult()).resolves.toBe('hello 1');
|
|
303
|
+
await expect(retrievedHandle.getStatus()).resolves.toMatchObject({
|
|
304
|
+
status: StatusString.SUCCESS,
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('endpoint-handler-WFID', async () => {
|
|
309
|
+
const workflowID = randomUUID();
|
|
310
|
+
const response = await request(app.callback()).get('/handler/bob').set({ 'dbos-idempotency-key': workflowID });
|
|
311
|
+
expect(response.statusCode).toBe(200);
|
|
312
|
+
expect(response.text).toBe('hello 1');
|
|
313
|
+
|
|
314
|
+
// Retrieve the workflow with WFID.
|
|
315
|
+
const retrievedHandle = DBOS.retrieveWorkflow(workflowID);
|
|
316
|
+
expect(retrievedHandle).not.toBeNull();
|
|
317
|
+
await expect(retrievedHandle.getResult()).resolves.toBe('hello 1');
|
|
318
|
+
await expect(retrievedHandle.getStatus()).resolves.toMatchObject({
|
|
319
|
+
status: StatusString.SUCCESS,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
async function testAuthMiddlware(ctx: DBOSKoaAuthContext) {
|
|
324
|
+
if (ctx.requiredRole.length > 0) {
|
|
325
|
+
const { userid } = ctx.koaContext.request.query;
|
|
326
|
+
const uid = userid?.toString();
|
|
327
|
+
|
|
328
|
+
if (!uid || uid.length === 0) {
|
|
329
|
+
const err = new DBOSErrors.DBOSNotAuthorizedError('Not logged in.', 401);
|
|
330
|
+
throw err;
|
|
331
|
+
} else {
|
|
332
|
+
if (uid === 'go_away') {
|
|
333
|
+
throw new DBOSErrors.DBOSNotAuthorizedError('Go away.', 401);
|
|
334
|
+
}
|
|
335
|
+
return Promise.resolve({
|
|
336
|
+
authenticatedUser: uid,
|
|
337
|
+
authenticatedRoles: uid === 'a_real_user' ? ['user'] : ['other'],
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@dhttp.authentication(testAuthMiddlware)
|
|
345
|
+
@dhttp.koaBodyParser(
|
|
346
|
+
bodyParser({
|
|
347
|
+
extendTypes: {
|
|
348
|
+
json: ['application/json', 'application/custom-content-type'],
|
|
349
|
+
},
|
|
350
|
+
encoding: 'utf-8',
|
|
351
|
+
parsedMethods: ['POST', 'PUT', 'PATCH', 'GET', 'DELETE'],
|
|
352
|
+
}),
|
|
353
|
+
)
|
|
354
|
+
@DefaultArgRequired
|
|
355
|
+
class TestEndpoints {
|
|
356
|
+
@dhttp.getApi('/hello')
|
|
357
|
+
static async hello() {
|
|
358
|
+
return Promise.resolve({ message: 'hello!' });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@dhttp.getApi('/hello/:id')
|
|
362
|
+
static async helloUrl(id: string) {
|
|
363
|
+
// Customize status code and response.
|
|
364
|
+
DBOSKoa.koaContext.body = `wow ${id}`;
|
|
365
|
+
DBOSKoa.koaContext.status = 301;
|
|
366
|
+
return Promise.resolve(`hello ${id}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
@dhttp.getApi('/redirect')
|
|
370
|
+
static async redirectUrl() {
|
|
371
|
+
const url = DBOSKoa.httpRequest.url || 'bad url'; // Get the raw url from request.
|
|
372
|
+
DBOSKoa.koaContext.redirect(url + '-dbos');
|
|
373
|
+
return Promise.resolve();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@dhttp.getApi('/check-url')
|
|
377
|
+
@DBOS.workflow()
|
|
378
|
+
static async returnURL() {
|
|
379
|
+
const url = DBOSKoa.httpRequest.url || 'bad url'; // Get the raw url from request.
|
|
380
|
+
return Promise.resolve(url);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@dhttp.getApi('/query')
|
|
384
|
+
static async helloQuery(name: string) {
|
|
385
|
+
DBOS.logger.info(`query with name ${name}`); // Test logging.
|
|
386
|
+
return Promise.resolve(`hello ${name}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@dhttp.getApi('/querybody')
|
|
390
|
+
static async helloQueryBody(name: string) {
|
|
391
|
+
DBOS.logger.info(`query with name ${name}`); // Test logging.
|
|
392
|
+
return Promise.resolve(`hello ${name}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@dhttp.deleteApi('/testdeletequery')
|
|
396
|
+
static async testdeletequeryparam(name: string) {
|
|
397
|
+
DBOS.logger.info(`delete with param from query with name ${name}`);
|
|
398
|
+
return Promise.resolve(`hello ${name}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@dhttp.deleteApi('/testdeleteurl/:name')
|
|
402
|
+
static async testdeleteurlparam(name: string) {
|
|
403
|
+
DBOS.logger.info(`delete with param from url with name ${name}`);
|
|
404
|
+
return Promise.resolve(`hello ${name}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@dhttp.deleteApi('/testdeletebody')
|
|
408
|
+
static async testdeletebodyparam(name: string) {
|
|
409
|
+
DBOS.logger.info(`delete with param from url with name ${name}`);
|
|
410
|
+
return Promise.resolve(`hello ${name}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
@dhttp.postApi('/testpost')
|
|
414
|
+
static async testpost(name: string) {
|
|
415
|
+
return Promise.resolve(`hello ${name}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
@dhttp.putApi('/testput')
|
|
419
|
+
static async testput(name: string) {
|
|
420
|
+
return Promise.resolve(`hello ${name}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
@dhttp.patchApi('/testpatch')
|
|
424
|
+
static async testpatch(name: string) {
|
|
425
|
+
return Promise.resolve(`hello ${name}`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
@dhttp.getApi('/dbos-error')
|
|
429
|
+
@DBOS.transaction()
|
|
430
|
+
static async dbosErr() {
|
|
431
|
+
return Promise.reject(new DBOSResponseError('customize error', 503));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
@dhttp.getApi('/handler/:name')
|
|
435
|
+
static async testHandler(name: string) {
|
|
436
|
+
const workflowID: string = DBOSKoa.koaContext.get(WorkflowIDHeader);
|
|
437
|
+
// Invoke a workflow using the given ID.
|
|
438
|
+
return DBOS.startWorkflow(TestEndpoints, { workflowID })
|
|
439
|
+
.testWorkflow(name)
|
|
440
|
+
.then((x) => x.getResult());
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
@dhttp.getApi('/testStartWorkflow/:name')
|
|
444
|
+
static async testStartWorkflow(name: string): Promise<string> {
|
|
445
|
+
return DBOS.startWorkflow(TestEndpoints)
|
|
446
|
+
.testWorkflow(name)
|
|
447
|
+
.then((x) => x.getResult());
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
@dhttp.getApi('/testInvokeWorkflow/:name')
|
|
451
|
+
static async testInvokeWorkflow(name: string): Promise<string> {
|
|
452
|
+
return await TestEndpoints.testWorkflow(name);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
@dhttp.postApi('/transaction/:name')
|
|
456
|
+
@DBOS.transaction()
|
|
457
|
+
static async testTransaction(name: string) {
|
|
458
|
+
const { rows } = await DBOS.pgClient.query<TestKvTable>(
|
|
459
|
+
`INSERT INTO ${testTableName}(id, value) VALUES (1, $1) RETURNING id`,
|
|
460
|
+
[name],
|
|
461
|
+
);
|
|
462
|
+
return `hello ${rows[0].id}`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
@dhttp.getApi('/step/:input')
|
|
466
|
+
@DBOS.step()
|
|
467
|
+
static async testStep(input: string) {
|
|
468
|
+
return Promise.resolve(input);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
@dhttp.postApi('/workflow')
|
|
472
|
+
@DBOS.workflow()
|
|
473
|
+
static async testWorkflow(name: string) {
|
|
474
|
+
const res = await TestEndpoints.testTransaction(name);
|
|
475
|
+
return TestEndpoints.testStep(res);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
@dhttp.postApi('/error')
|
|
479
|
+
@DBOS.workflow()
|
|
480
|
+
static async testWorkflowError(name: string) {
|
|
481
|
+
// This workflow should encounter duplicate primary key error.
|
|
482
|
+
let res = await TestEndpoints.testTransaction(name);
|
|
483
|
+
res = await TestEndpoints.testTransaction(name);
|
|
484
|
+
return res;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@dhttp.getApi('/requireduser')
|
|
488
|
+
@DBOS.requiredRole(['user'])
|
|
489
|
+
static async testAuth(name: string) {
|
|
490
|
+
if (DBOS.authenticatedUser !== 'a_real_user') {
|
|
491
|
+
throw new DBOSResponseError('uid not a real user!', 400);
|
|
492
|
+
}
|
|
493
|
+
if (!DBOS.authenticatedRoles.includes('user')) {
|
|
494
|
+
throw new DBOSResponseError("roles don't include user!", 400);
|
|
495
|
+
}
|
|
496
|
+
if (DBOS.assumedRole !== 'user') {
|
|
497
|
+
throw new DBOSResponseError('Should never happen! Not assumed to be user', 400);
|
|
498
|
+
}
|
|
499
|
+
return Promise.resolve(`Please say hello to ${name}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
@dhttp.getApi('/requireduser2')
|
|
503
|
+
@DBOS.requiredRole(['user'])
|
|
504
|
+
static async testAuth2(name: string) {
|
|
505
|
+
if (DBOS.authenticatedUser !== 'a_real_user') {
|
|
506
|
+
throw new DBOSResponseError('uid not a real user!', 400);
|
|
507
|
+
}
|
|
508
|
+
if (!DBOS.authenticatedRoles.includes('user')) {
|
|
509
|
+
throw new DBOSResponseError("roles don't include user!", 400);
|
|
510
|
+
}
|
|
511
|
+
if (DBOS.assumedRole !== 'user') {
|
|
512
|
+
throw new DBOSResponseError('Should never happen! Not assumed to be user', 400);
|
|
513
|
+
}
|
|
514
|
+
return Promise.resolve(`Please say hello to ${name}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
});
|