@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.
@@ -0,0 +1,539 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
+ import Koa from 'koa';
3
+ import Router from '@koa/router';
4
+
5
+ import {
6
+ ArgDate,
7
+ ArgOptional,
8
+ ArgRequired,
9
+ ArgVarchar,
10
+ DBOS,
11
+ DefaultArgOptional,
12
+ DefaultArgRequired,
13
+ } from '@dbos-inc/dbos-sdk';
14
+
15
+ import { DBOSKoa } from '../src';
16
+
17
+ import request from 'supertest';
18
+
19
+ const dhttp = new DBOSKoa();
20
+
21
+ describe('httpserver-datavalidation-tests', () => {
22
+ let app: Koa;
23
+ let appRouter: Router;
24
+
25
+ beforeAll(async () => {
26
+ DBOS.setConfig({
27
+ name: 'koa-validation',
28
+ });
29
+ const _classes = [
30
+ TestEndpointDataVal,
31
+ DefaultArgToDefault,
32
+ DefaultArgToOptional,
33
+ DefaultArgToRequired,
34
+ ArgNotMentioned,
35
+ ];
36
+ await DBOS.launch();
37
+ app = new Koa();
38
+ appRouter = new Router();
39
+ dhttp.registerWithApp(app, appRouter);
40
+ });
41
+
42
+ afterAll(async () => {
43
+ await DBOS.shutdown();
44
+ });
45
+
46
+ test('get-hello', async () => {
47
+ const response = await request(app.callback()).get('/hello');
48
+ expect(response.statusCode).toBe(200);
49
+ expect(response.body.message).toBe('hello!');
50
+ });
51
+
52
+ test('not-there', async () => {
53
+ const response = await request(app.callback()).get('/nourl');
54
+ expect(response.statusCode).toBe(404);
55
+ });
56
+
57
+ // Plain string
58
+ test('no string (get)', async () => {
59
+ const response = await request(app.callback()).get('/string');
60
+ expect(response.statusCode).toBe(400);
61
+ });
62
+ test('no string (post)', async () => {
63
+ const response = await request(app.callback()).post('/string');
64
+ expect(response.statusCode).toBe(400);
65
+ });
66
+ test('no string (post) 2', async () => {
67
+ const response = await request(app.callback()).post('/string').send({});
68
+ expect(response.statusCode).toBe(400);
69
+ });
70
+ test('no string (post) - something else', async () => {
71
+ const response = await request(app.callback()).post('/string').send({ foo: 'bar' });
72
+ expect(response.statusCode).toBe(400);
73
+ });
74
+ test('string get', async () => {
75
+ const response = await request(app.callback()).get('/string').query({ v: 'AAA' });
76
+ expect(response.statusCode).toBe(200);
77
+ });
78
+ test('string post', async () => {
79
+ const response = await request(app.callback()).post('/string').send({ v: 'AAA' });
80
+ expect(response.statusCode).toBe(200);
81
+ });
82
+ test('string post not a number', async () => {
83
+ const response = await request(app.callback()).post('/string').send({ v: 1234 });
84
+ expect(response.statusCode).toBe(400);
85
+ });
86
+
87
+ // No string to optional arg -- in a workflow
88
+ test('no string to workflow w optional arg', async () => {
89
+ const response = await request(app.callback()).post('/doworkflow').send({});
90
+ expect(response.statusCode).toBe(200);
91
+ });
92
+
93
+ // Varchar(10)
94
+ test('no string (get)', async () => {
95
+ const response = await request(app.callback()).get('/varchar');
96
+ expect(response.statusCode).toBe(400);
97
+ });
98
+ test('no string (post)', async () => {
99
+ const response = await request(app.callback()).post('/varchar');
100
+ expect(response.statusCode).toBe(400);
101
+ });
102
+ test('no string (post) 2', async () => {
103
+ const response = await request(app.callback()).post('/varchar').send({});
104
+ expect(response.statusCode).toBe(400);
105
+ });
106
+ test('string get', async () => {
107
+ const response = await request(app.callback()).get('/varchar').query({ v: 'AAA' });
108
+ expect(response.statusCode).toBe(200);
109
+ });
110
+ test('string get - too long', async () => {
111
+ const response = await request(app.callback()).get('/varchar').query({ v: 'AAAaaaAAAaaa' });
112
+ expect(response.statusCode).toBe(400);
113
+ });
114
+ test('string post', async () => {
115
+ const response = await request(app.callback()).post('/varchar').send({ v: 'AAA' });
116
+ expect(response.statusCode).toBe(200);
117
+ });
118
+ test('string post - too long', async () => {
119
+ const response = await request(app.callback()).post('/varchar').send({ v: 'AAAaaaAAAaaa' });
120
+ expect(response.statusCode).toBe(400);
121
+ });
122
+ test('string post not a number', async () => {
123
+ const response = await request(app.callback()).post('/varchar').send({ v: 1234 });
124
+ expect(response.statusCode).toBe(400);
125
+ });
126
+ test('varchar post boolean', async () => {
127
+ const response = await request(app.callback()).post('/number').send({ v: false });
128
+ expect(response.statusCode).toBe(400);
129
+ });
130
+
131
+ // Number (float)
132
+ test('no number (get)', async () => {
133
+ const response = await request(app.callback()).get('/number');
134
+ expect(response.statusCode).toBe(400);
135
+ });
136
+ test('no number (post)', async () => {
137
+ const response = await request(app.callback()).post('/number');
138
+ expect(response.statusCode).toBe(400);
139
+ });
140
+ test('no number (post) 2', async () => {
141
+ const response = await request(app.callback()).post('/number').send({});
142
+ expect(response.statusCode).toBe(400);
143
+ });
144
+ test('number get', async () => {
145
+ const response = await request(app.callback()).get('/number').query({ v: '10.1' });
146
+ expect(response.statusCode).toBe(200);
147
+ });
148
+ test('number get', async () => {
149
+ const response = await request(app.callback()).get('/number').query({ v: 10.5 });
150
+ expect(response.statusCode).toBe(200);
151
+ });
152
+ test('number get - bogus value', async () => {
153
+ const response = await request(app.callback()).get('/number').query({ v: 'abc' });
154
+ expect(response.statusCode).toBe(400);
155
+ });
156
+ test('number get - bigint', async () => {
157
+ const response = await request(app.callback()).get('/number').query({ v: 12345678901234567890n });
158
+ expect(response.statusCode).toBe(200);
159
+ });
160
+ test('number post', async () => {
161
+ const response = await request(app.callback()).post('/number').send({ v: '20' });
162
+ expect(response.statusCode).toBe(200);
163
+ });
164
+ test('number post', async () => {
165
+ const response = await request(app.callback()).post('/number').send({ v: 20.2 });
166
+ expect(response.statusCode).toBe(200);
167
+ });
168
+ /* This fails for unknown reasons
169
+ test("number post", async () => {
170
+ const response = await request(app.callback()).post("/number")
171
+ .send({v:0});
172
+ expect(response.statusCode).toBe(200);
173
+ });
174
+ */
175
+ test('number post', async () => {
176
+ const response = await request(app.callback()).post('/number').send({ v: -1 });
177
+ expect(response.statusCode).toBe(200);
178
+ });
179
+ test('number post - bogus value', async () => {
180
+ const response = await request(app.callback()).post('/number').send({ v: 'AAAaaaAAAaaa' });
181
+ expect(response.statusCode).toBe(400);
182
+ });
183
+ test('number post not a number', async () => {
184
+ const response = await request(app.callback()).post('/number').send({ v: false });
185
+ expect(response.statusCode).toBe(400);
186
+ });
187
+ /* You can't do this - no bigint serialize to json
188
+ test("number post bigint", async () => {
189
+ const response = await request(app.callback()).post("/number")
190
+ .send({v:234567890123456789n});
191
+ expect(response.statusCode).toBe(200);
192
+ });
193
+ */
194
+ test('number post bigint', async () => {
195
+ const response = await request(app.callback()).post('/number').send({ v: '12345678901234567890' });
196
+ expect(response.statusCode).toBe(200);
197
+ });
198
+
199
+ // Boolean
200
+ test('no boolean (get)', async () => {
201
+ const response = await request(app.callback()).get('/boolean');
202
+ expect(response.statusCode).toBe(400);
203
+ });
204
+ test('no boolean (post)', async () => {
205
+ const response = await request(app.callback()).post('/boolean');
206
+ expect(response.statusCode).toBe(400);
207
+ });
208
+ test('no boolean (post) 2', async () => {
209
+ const response = await request(app.callback()).post('/boolean').send({});
210
+ expect(response.statusCode).toBe(400);
211
+ });
212
+
213
+ test('true boolean (get)', async () => {
214
+ const response = await request(app.callback()).get('/boolean').query({ v: 'true' });
215
+ expect(response.statusCode).toBe(200);
216
+ expect(response.body.message).toBe('This is a really nice boolean: true');
217
+ });
218
+ test('true boolean (get) 2', async () => {
219
+ const response = await request(app.callback()).get('/boolean').query({ v: true });
220
+ expect(response.statusCode).toBe(200);
221
+ expect(response.body.message).toBe('This is a really nice boolean: true');
222
+ });
223
+ test('true boolean (get) 3', async () => {
224
+ const response = await request(app.callback()).get('/boolean').query({ v: 1 });
225
+ expect(response.statusCode).toBe(200);
226
+ expect(response.body.message).toBe('This is a really nice boolean: true');
227
+ });
228
+ test('false boolean (get)', async () => {
229
+ const response = await request(app.callback()).get('/boolean').query({ v: 'F' });
230
+ expect(response.statusCode).toBe(200);
231
+ expect(response.body.message).toBe('This is a really nice boolean: false');
232
+ });
233
+ test('false boolean (get) 2', async () => {
234
+ const response = await request(app.callback()).get('/boolean').query({ v: false });
235
+ expect(response.statusCode).toBe(200);
236
+ expect(response.body.message).toBe('This is a really nice boolean: false');
237
+ });
238
+ test('false boolean (get) 3', async () => {
239
+ const response = await request(app.callback()).get('/boolean').query({ v: 0 });
240
+ expect(response.statusCode).toBe(200);
241
+ expect(response.body.message).toBe('This is a really nice boolean: false');
242
+ });
243
+
244
+ test('true boolean (post)', async () => {
245
+ const response = await request(app.callback()).post('/boolean').send({ v: 'true' });
246
+ expect(response.statusCode).toBe(200);
247
+ expect(response.body.message).toBe('This is a really nice boolean: true');
248
+ });
249
+ test('true boolean (post) 2', async () => {
250
+ const response = await request(app.callback()).post('/boolean').send({ v: true });
251
+ expect(response.statusCode).toBe(200);
252
+ expect(response.body.message).toBe('This is a really nice boolean: true');
253
+ });
254
+ test('true boolean (post) 3', async () => {
255
+ const response = await request(app.callback()).post('/boolean').send({ v: 1 });
256
+ expect(response.statusCode).toBe(200);
257
+ expect(response.body.message).toBe('This is a really nice boolean: true');
258
+ });
259
+ test('false boolean (post)', async () => {
260
+ const response = await request(app.callback()).post('/boolean').send({ v: 'F' });
261
+ expect(response.statusCode).toBe(200);
262
+ expect(response.body.message).toBe('This is a really nice boolean: false');
263
+ });
264
+ test('bad boolean 1', async () => {
265
+ const response = await request(app.callback()).post('/boolean').send({ v: 'A' });
266
+ expect(response.statusCode).toBe(400);
267
+ });
268
+ test('bad boolean 2', async () => {
269
+ const response = await request(app.callback()).post('/boolean').send({ v: 'falsy' });
270
+ expect(response.statusCode).toBe(400);
271
+ });
272
+ test('bad boolean 1', async () => {
273
+ const response = await request(app.callback()).post('/boolean').send({ v: 2 });
274
+ expect(response.statusCode).toBe(400);
275
+ });
276
+
277
+ // Date
278
+ test('no date (get)', async () => {
279
+ const response = await request(app.callback()).get('/date');
280
+ expect(response.statusCode).toBe(400);
281
+ });
282
+ test('no date (post)', async () => {
283
+ const response = await request(app.callback()).post('/date');
284
+ expect(response.statusCode).toBe(400);
285
+ });
286
+ test('no date (post) 2', async () => {
287
+ const response = await request(app.callback()).post('/date').send({});
288
+ expect(response.statusCode).toBe(400);
289
+ });
290
+ test('good date (get)', async () => {
291
+ const response = await request(app.callback()).get('/date').query({ v: '2023-10-31' });
292
+ expect(response.statusCode).toBe(200);
293
+ });
294
+ test('good date (post)', async () => {
295
+ const response = await request(app.callback()).post('/date').send({ v: '2023-10-31' });
296
+ expect(response.statusCode).toBe(200);
297
+ });
298
+ test('bad date (get)', async () => {
299
+ const response = await request(app.callback()).get('/date').query({ v: 'AAA' });
300
+ expect(response.statusCode).toBe(400);
301
+ });
302
+ test('bad date (post)', async () => {
303
+ const response = await request(app.callback()).post('/date').send({ v: 'turnip' });
304
+ expect(response.statusCode).toBe(400);
305
+ });
306
+
307
+ test('defined or not', async () => {
308
+ const attempts = [
309
+ ['/rrequired', undefined, 400],
310
+ ['/rrequired', 'hasaval', 200],
311
+ ['/rdefault', undefined, 400],
312
+ ['/rdefault', 'hasaval', 200],
313
+ ['/roptional', undefined, 200],
314
+ ['/roptional', 'hasaval', 200],
315
+
316
+ ['/orequired', undefined, 400],
317
+ ['/orequired', 'hasaval', 200],
318
+ ['/odefault', undefined, 200],
319
+ ['/odefault', 'hasaval', 200],
320
+ ['/ooptional', undefined, 200],
321
+ ['/ooptional', 'hasaval', 200],
322
+
323
+ ['/drequired', undefined, 400],
324
+ ['/drequired', 'hasaval', 200],
325
+ ['/ddefault', undefined, 200],
326
+ ['/ddefault', 'hasaval', 200],
327
+ ['/doptional', undefined, 200],
328
+ ['/doptional', 'hasaval', 200],
329
+
330
+ ['/nrequired', undefined, 200],
331
+ ['/nrequired', 'hasaval', 200],
332
+ ['/ndefault', undefined, 200],
333
+ ['/ndefault', 'hasaval', 200],
334
+ ['/noptional', undefined, 200],
335
+ ['/noptional', 'hasaval', 200],
336
+ ];
337
+
338
+ for (const v of attempts) {
339
+ const response = await request(app.callback())
340
+ .post(v[0] as string)
341
+ .send({ v: v[1] });
342
+ if (response.statusCode !== v[2]) {
343
+ console.warn(`${v[0]} ${v[1]} ${v[2]} - ${response.statusCode}`);
344
+ }
345
+ expect(response.statusCode).toBe(v[2]);
346
+ }
347
+ });
348
+
349
+ @DefaultArgRequired
350
+ class TestEndpointDataVal {
351
+ @dhttp.getApi('/hello')
352
+ static async hello() {
353
+ return Promise.resolve({ message: 'hello!' });
354
+ }
355
+
356
+ @dhttp.getApi('/string')
357
+ static async checkStringG(v: string) {
358
+ if (typeof v !== 'string') {
359
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
360
+ }
361
+ return Promise.resolve({ message: `This is a really nice string: ${v}` });
362
+ }
363
+
364
+ @dhttp.postApi('/string')
365
+ static async checkStringP(v: string) {
366
+ if (typeof v !== 'string') {
367
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
368
+ }
369
+ return Promise.resolve({ message: `This is a really nice string: ${v}` });
370
+ }
371
+
372
+ @dhttp.getApi('/varchar')
373
+ static async checkVarcharG(@ArgVarchar(10) v: string) {
374
+ if (typeof v !== 'string') {
375
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
376
+ }
377
+ return Promise.resolve({ message: `This is a really nice string (limited length): ${v}` });
378
+ }
379
+
380
+ @dhttp.postApi('/varchar')
381
+ static async checkVarcharP(@ArgVarchar(10) v: string) {
382
+ if (typeof v !== 'string') {
383
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
384
+ }
385
+ return Promise.resolve({ message: `This is a really nice string (limited length): ${v}` });
386
+ }
387
+
388
+ @dhttp.getApi('/number')
389
+ static async checkNumberG(v: number) {
390
+ if (typeof v !== 'number') {
391
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
392
+ }
393
+ return Promise.resolve({ message: `This is a really nice number: ${v}` });
394
+ }
395
+
396
+ @dhttp.postApi('/number')
397
+ static async checkNumberP(v: number) {
398
+ if (typeof v !== 'number') {
399
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
400
+ }
401
+ return Promise.resolve({ message: `This is a really nice number: ${v}` });
402
+ }
403
+
404
+ @dhttp.getApi('/bigint')
405
+ static async checkBigintG(v: bigint) {
406
+ if (typeof v !== 'bigint') {
407
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
408
+ }
409
+ return Promise.resolve({ message: `This is a really nice bigint: ${v}` });
410
+ }
411
+
412
+ @dhttp.postApi('/bigint')
413
+ static async checkBigintP(v: bigint) {
414
+ if (typeof v !== 'bigint') {
415
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
416
+ }
417
+ return Promise.resolve({ message: `This is a really nice bigint: ${v}` });
418
+ }
419
+
420
+ @dhttp.getApi('/date')
421
+ static async checkDateG(@ArgDate() v: Date) {
422
+ if (!(v instanceof Date)) {
423
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
424
+ }
425
+ return Promise.resolve({ message: `This is a really nice date: ${v.toISOString()}` });
426
+ }
427
+
428
+ @dhttp.postApi('/date')
429
+ static async checkDateP(@ArgDate() v: Date) {
430
+ if (!(v instanceof Date)) {
431
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
432
+ }
433
+ return Promise.resolve({ message: `This is a really nice date: ${v.toISOString()}` });
434
+ }
435
+
436
+ // This is in honor of Harry
437
+ @dhttp.getApi('/boolean')
438
+ static async checkBooleanG(v: boolean) {
439
+ if (typeof v !== 'boolean') {
440
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
441
+ }
442
+ return Promise.resolve({ message: `This is a really nice boolean: ${v}` });
443
+ }
444
+
445
+ @dhttp.postApi('/boolean')
446
+ static async checkBooleanP(v: boolean) {
447
+ if (typeof v !== 'boolean') {
448
+ return Promise.reject(new Error('THIS SHOULD NEVER HAPPEN'));
449
+ }
450
+ return Promise.resolve({ message: `This is a really nice boolean: ${v}` });
451
+ }
452
+
453
+ // Types saved for another day - even the decorators are not there yet:
454
+ // Integer - not working
455
+ // Decimal
456
+ // UUID?
457
+ // JSON
458
+ }
459
+
460
+ @DefaultArgRequired
461
+ class DefaultArgToRequired {
462
+ @dhttp.postApi('/rrequired')
463
+ static async checkReqValueR(@ArgRequired v: string) {
464
+ return Promise.resolve({ message: `Got string ${v}` });
465
+ }
466
+
467
+ @dhttp.postApi('/roptional')
468
+ static async checkOptValueR(@ArgOptional v?: string) {
469
+ return Promise.resolve({ message: `Got string ${v}` });
470
+ }
471
+
472
+ @dhttp.postApi('/rdefault')
473
+ static async checkDefValueR(v?: string) {
474
+ return Promise.resolve({ message: `Got string ${v}` });
475
+ }
476
+ }
477
+
478
+ @DefaultArgOptional
479
+ class DefaultArgToOptional {
480
+ @dhttp.postApi('/orequired')
481
+ static async checkReqValueO(@ArgRequired v: string) {
482
+ return Promise.resolve({ message: `Got string ${v}` });
483
+ }
484
+
485
+ @dhttp.postApi('/ooptional')
486
+ static async checkOptValueO(@ArgOptional v?: string) {
487
+ return Promise.resolve({ message: `Got string ${v}` });
488
+ }
489
+
490
+ @dhttp.postApi('/odefault')
491
+ static async checkDefValueO(v?: string) {
492
+ return Promise.resolve({ message: `Got string ${v}` });
493
+ }
494
+ }
495
+
496
+ class DefaultArgToDefault {
497
+ @dhttp.postApi('/drequired')
498
+ static async checkReqValueD(@ArgRequired v: string) {
499
+ return Promise.resolve({ message: `Got string ${v}` });
500
+ }
501
+
502
+ @dhttp.postApi('/doptional')
503
+ static async checkOptValueD(@ArgOptional v?: string) {
504
+ return Promise.resolve({ message: `Got string ${v}` });
505
+ }
506
+
507
+ @dhttp.postApi('/ddefault')
508
+ static async checkDefValueD(v?: string) {
509
+ return Promise.resolve({ message: `Got string ${v}` });
510
+ }
511
+
512
+ @DBOS.workflow()
513
+ static async opworkflow(@ArgOptional v?: string) {
514
+ return Promise.resolve({ message: v });
515
+ }
516
+
517
+ @dhttp.postApi('/doworkflow')
518
+ static async doWorkflow(@ArgOptional v?: string) {
519
+ return await DefaultArgToDefault.opworkflow(v);
520
+ }
521
+ }
522
+
523
+ class ArgNotMentioned {
524
+ @dhttp.postApi('/nrequired')
525
+ static async checkReqValueO(v: string) {
526
+ return Promise.resolve({ message: `Got string ${v}` });
527
+ }
528
+
529
+ @dhttp.postApi('/noptional')
530
+ static async checkOptValueO(v?: string) {
531
+ return Promise.resolve({ message: `Got string ${v}` });
532
+ }
533
+
534
+ @dhttp.postApi('/ndefault')
535
+ static async checkDefValueO(v: string = 'b') {
536
+ return Promise.resolve({ message: `Got string ${v}` });
537
+ }
538
+ }
539
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ /* Visit https://aka.ms/tsconfig to read more about this file */
2
+ {
3
+ "extends": "../../tsconfig.shared.json",
4
+ "compilerOptions": {
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["src/"],
8
+ "exclude": ["dist"]
9
+ }