@dbos-inc/koa-serve 3.5.44-preview.gc094fdab44 → 3.6.3-preview

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