Skip to content

Commit eb64c72

Browse files
authored
Merge pull request #186 from lemoncloud-io/v4.0
V4.0
2 parents ee5c198 + e48bf70 commit eb64c72

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+14893
-141
lines changed

data/mocks/mock.error-json.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"!": "error case with JSON string",
3+
"param": {
4+
"method": "GET",
5+
"endpoint": "https://api.lemoncloud.io/hello/test",
6+
"id": "error-json"
7+
},
8+
"data": null,
9+
"error": "{\"message\":\"JSON error\",\"code\":500}"
10+
}

data/mocks/mock.error-object.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"!": "error case with error object",
3+
"param": {
4+
"method": "GET",
5+
"endpoint": "https://api.lemoncloud.io/hello/test",
6+
"id": "error-object"
7+
},
8+
"data": null,
9+
"error": {
10+
"message": "Error object",
11+
"statusCode": 400
12+
}
13+
}

data/mocks/mock.error-string.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"!": "error case with plain string",
3+
"param": {
4+
"method": "GET",
5+
"endpoint": "https://api.lemoncloud.io/hello/test",
6+
"id": "error-string"
7+
},
8+
"data": null,
9+
"error": "Plain string error message"
10+
}

src/common/test-helper.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ describe('TestHelper', () => {
100100
//* test waited()
101101
it('should pass waited() by 200msec', async () => {
102102
const t1 = new Date().getTime();
103-
expect2(await waited()).toEqual(undefined);
103+
const result = await waited();
104+
expect2(() => result).toEqual(undefined);
104105
const t2 = new Date().getTime();
105106
expect2(t2 - t1 >= 200).toEqual(true);
106107
});

src/controllers/dummy-controller.spec.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,87 @@ describe('DummyController', () => {
138138
expect2(controller.asFuncName('GET', 'hello', '-')).toEqual('getHello_');
139139
expect2(controller.asFuncName('GET', 'hello', '-_--')).toEqual('getHello____');
140140
expect2(controller.asFuncName('GET', 'hello', '-Me')).toEqual('getHelloMe');
141+
142+
// decode test
143+
const decoded1 = controller.decode('GET', 'test', '');
144+
expect2(typeof decoded1).toEqual('function');
145+
146+
const decoded2 = controller.decode('GET', 'test', 'custom');
147+
expect2(decoded2).toEqual(null); // non-exist handler
148+
149+
const decoded3 = controller.decode('LIST', '', '');
150+
expect2(typeof decoded3).toEqual('function'); // do_list handler exist
151+
});
152+
153+
//* test constructor with different parameters
154+
it('should pass constructor with different idName', async () => {
155+
//* test with custom idName (using existing data file: dummy-controller-data.yml)
156+
const { controller: controller1 } = instance(type, name, '_id');
157+
expect2(() => controller1.hello()).toEqual(`dummy-controller:${type}/${name}`);
158+
159+
//* test with name undefined (defaults to type) - dummy-controller-data.yml exists
160+
const { controller: controller2 } = instance('controller', undefined, 'id');
161+
expect2(() => controller2.hello()).toEqual(`dummy-controller:controller/controller`);
162+
163+
//* test with different type and name - using existing 'account' data
164+
const { controller: controller3 } = instance('user', 'account', 'id');
165+
expect2(() => controller3.hello()).toEqual(`dummy-controller:user/account`);
166+
});
167+
168+
//* test decode method
169+
it('should pass decode() edge cases', async () => {
170+
const { controller } = instance(type, name);
171+
172+
//* decode with various mode values
173+
const decodedGet = controller.decode('GET', 'test-id', '');
174+
expect2(typeof decodedGet).toEqual('function');
175+
176+
const decodedPost = controller.decode('POST', 'test-id', '');
177+
expect2(typeof decodedPost).toEqual('function');
178+
179+
const decodedPut = controller.decode('PUT', 'test-id', '');
180+
expect2(typeof decodedPut).toEqual('function');
181+
182+
const decodedDelete = controller.decode('DELETE', 'test-id', '');
183+
expect2(typeof decodedDelete).toEqual('function');
184+
185+
const decodedList = controller.decode('LIST', '', '');
186+
expect2(typeof decodedList).toEqual('function');
187+
188+
//* decode with cmd (should find handler via super.decode first)
189+
const decodedWithCmd = controller.decode('GET', 'test-id', 'custom');
190+
expect2(decodedWithCmd).toEqual(null);
191+
192+
//* decode with patch mode (no handler exists for PATCH)
193+
const decodedPatch = controller.decode('PATCH', 'test-id', '');
194+
expect2(decodedPatch).toEqual(null);
195+
196+
//* test super.decode finding a handler (line 60 coverage)
197+
//* add a method that super.decode can find via asFuncName pattern
198+
(controller as any).getDummy = () => 'test-handler';
199+
const decodedSuper = controller.decode('GET', 'test-id', '');
200+
expect2(typeof decodedSuper).toEqual('function');
201+
202+
//* test do_list
203+
expect2(await controller.do_list('', null, null, null).catch(GETERR)).toEqual({
204+
limit: 1,
205+
list: [{ id: 'A0', type: 'user', name: 'lemon' }],
206+
page: 1,
207+
total: 2,
208+
});
209+
210+
expect2(await controller.do_list('', undefined, null, null).catch(GETERR)).toEqual({
211+
limit: 1,
212+
list: [{ id: 'A0', type: 'user', name: 'lemon' }],
213+
page: 1,
214+
total: 2,
215+
});
216+
217+
expect2(await controller.do_list('', {}, null, null).catch(GETERR)).toEqual({
218+
limit: 1,
219+
list: [{ id: 'A0', type: 'user', name: 'lemon' }],
220+
page: 1,
221+
total: 2,
222+
});
141223
});
142224
});

src/controllers/general-api-controller.spec.ts

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @copyright (C) 2020 LemonCloud Co Ltd. - All Rights Reserved.
99
*/
1010
import { LambdaWEBHandler, LambdaHandler } from '../cores/lambda';
11-
import { ProtocolParam, Elastic6QueryService } from '../cores';
11+
import { ProtocolParam, Elastic6QueryService, SimpleSearchParam } from '../cores';
1212
import { buildEngine } from '../engine';
1313
import { buildExpress } from '../tools';
1414
import { expect2, GETERR } from '../common/test-helper';
@@ -43,12 +43,21 @@ class MyGeneralAPIController extends GeneralAPIController<TypedStorageService<$p
4343
}
4444

4545
//* create instance.
46-
export const instance = (type: 'dummy', unique?: string) => {
46+
export const instance = (type: 'dummy', unique?: string, withSearch = false) => {
4747
const { storage2 } = $proxy.instance(type == 'dummy' ? 'dummy-account-data.yml' : type);
4848
const $lambda = new LambdaHandler();
4949
const $web = new LambdaWEBHandlerLocal($lambda);
5050
const storage = storage2.makeTypedStorageService('test');
51-
const controller = new MyGeneralAPIController('test', storage, null, unique);
51+
//* create mock search service if requested
52+
const search = withSearch
53+
? ({
54+
searchSimple: async (param: SimpleSearchParam) => {
55+
return { total: 0, list: [] as any[], page: param?.$page || 0, limit: param?.$limit || 12 };
56+
},
57+
} as any)
58+
: null;
59+
const controller = new MyGeneralAPIController('test', storage, search, unique);
60+
5261
$web.addController(controller);
5362
//* build engine + express.app.
5463
const $engine = buildEngine({});
@@ -411,5 +420,118 @@ describe('GeneralController', () => {
411420
expect2(await storage.read('#name/BBB').catch(GETERR), 'id,type,stereo,meta').toEqual(
412421
'404 NOT FOUND - _id:TT:test:#name/BBB',
413422
); // lookup-data (no exists)
423+
424+
//* listBase test - search is null
425+
try {
426+
await request(app).get('/test?page=1&limit=5');
427+
} catch (err) {
428+
expect2(err.message.includes('searchSimple')).toEqual(true);
429+
}
430+
431+
//* asFuncName test - base function mapping
432+
expect2(controller.asFuncName('GET', 'test', '')).toEqual('getBase'); // actually mapped to getBase
433+
expect2(controller.asFuncName('GET', 'base', '')).toEqual('getBase');
434+
});
435+
436+
//* test listBase with search service
437+
it('should pass listBase() with search service', async () => {
438+
const { controller, app } = instance('dummy', undefined, true);
439+
expect2(() => controller.hello()).toEqual('general-api-controller:test');
440+
441+
//* test listBase with default params
442+
expect2(await request(app).get('/test'), 'status,body').toEqual({
443+
status: 200,
444+
body: { total: 0, list: [], page: 0, limit: 12 },
445+
});
446+
447+
//* test listBase with custom page & limit
448+
expect2(await request(app).get('/test?page=2&limit=20'), 'status,body').toEqual({
449+
status: 200,
450+
body: { total: 0, list: [], page: 2, limit: 20 },
451+
});
452+
453+
//* test listBase with ipp (legacy parameter)
454+
expect2(await request(app).get('/test?page=1&ipp=10'), 'status,body').toEqual({
455+
status: 200,
456+
body: { total: 0, list: [], page: 1, limit: 10 },
457+
});
458+
});
459+
460+
//* test edge cases
461+
it('should pass edge cases', async () => {
462+
const { controller, app, storage } = instance('dummy', 'name', true);
463+
464+
//* test deleteBase with destroy and unique field
465+
//* first create an item
466+
const created = (await request(app).post('/test/edge1').send({ name: 'ToDelete' })).body;
467+
expect2(created, 'id,type,name').toEqual({ id: 'edge1', type: 'test', name: 'ToDelete' });
468+
469+
//* verify lookup was created
470+
expect2(await storage.read('#name/ToDelete').catch(GETERR), 'id,stereo,meta').toEqual({
471+
id: '#name/ToDelete',
472+
stereo: '#',
473+
meta: 'edge1',
474+
});
475+
476+
//* delete with destroy
477+
const deleteResult = await request(app).delete('/test/edge1?destroy');
478+
expect2(typeof deleteResult.body.deletedAt).toEqual('number');
479+
480+
//* verify both item and lookup are deleted
481+
expect2(await storage.read('edge1').catch(GETERR)).toEqual('404 NOT FOUND - _id:TT:test:edge1');
482+
expect2(await storage.read('#name/ToDelete').catch(GETERR)).toEqual(
483+
'404 NOT FOUND - _id:TT:test:#name/ToDelete',
484+
);
485+
486+
//* test listBase with null param
487+
const result1 = await controller.listBase('', null, null, null as any);
488+
expect2(result1, 'total,page,limit').toEqual({ total: 0, page: 0, limit: 12 });
489+
490+
//* test deleteBase with null param
491+
await controller.postBase('nulltest', null, { name: 'NullTest' }, null);
492+
const result2 = await controller.deleteBase('nulltest', null, null, null);
493+
expect2(result2.deletedAt > 0).toEqual(true);
494+
495+
//* test deleteBase with destroy on non-existent item
496+
//* this tests the `: ''` branch where $org is null
497+
await request(app).delete('/test/never-existed?destroy');
498+
499+
//* test postBase with id='0' to trigger auto-id generation
500+
const post0Result = await request(app).post('/test/0').send({ name: 'AutoID' });
501+
expect2(post0Result.body.type).toEqual('test');
502+
expect2(typeof post0Result.body.id).toEqual('string');
503+
504+
//* cleanup
505+
if (post0Result.body.id) await storage.delete(post0Result.body.id, true);
506+
507+
//* test putBase with various id types
508+
await controller.postBase('testput2', null, { name: 'TestPut2' }, null);
509+
const putResult = await controller.putBase('testput2' as any, null, { extra: 'data' }, null);
510+
expect2(putResult, 'id').toEqual({ id: 'testput2' });
511+
512+
//* test postBase with id parameter
513+
const postResult = await controller.postBase('testpost2' as any, null, { name: 'TestPost2' }, null);
514+
expect2(postResult, 'id').toEqual({ id: 'testpost2' });
515+
516+
//* test with id that has whitespace
517+
await controller.postBase(' testid3 ', null, { name: 'TrimTest' }, null);
518+
const trimResult = await controller.putBase(' testid3 ' as any, null, { data: 'trimmed' }, null);
519+
expect2(trimResult, 'id').toEqual({ id: 'testid3' });
520+
521+
//* test with falsy id
522+
expect2(await controller.putBase(null as any, null, {}, null).catch(GETERR)).toEqual(
523+
'@id (string) is required!',
524+
);
525+
expect2(await controller.postBase(undefined as any, null, {}, null).catch(GETERR)).toEqual(
526+
'@id (string) is required!',
527+
);
528+
529+
//* cleanup
530+
await storage.delete('testput2', true);
531+
await storage.delete('testpost2', true);
532+
await storage.delete('testid3', true);
533+
await storage.delete('#name/TestPut2', true);
534+
await storage.delete('#name/TestPost2', true);
535+
await storage.delete('#name/TrimTest', true);
414536
});
415537
});

0 commit comments

Comments
 (0)