@objectstack/runtime 4.0.2 → 4.0.4
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +19 -0
- package/dist/index.cjs +225 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -11
- package/dist/index.d.ts +9 -11
- package/dist/index.js +225 -193
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/app-plugin.ts +9 -0
- package/src/dispatcher-plugin.ts +93 -59
- package/src/http-dispatcher.root.test.ts +0 -3
- package/src/http-dispatcher.test.ts +105 -100
- package/src/http-dispatcher.ts +183 -187
- package/vitest.config.ts +1 -0
|
@@ -7,24 +7,35 @@ describe('HttpDispatcher', () => {
|
|
|
7
7
|
let kernel: ObjectKernel;
|
|
8
8
|
let dispatcher: HttpDispatcher;
|
|
9
9
|
let mockProtocol: any;
|
|
10
|
-
let
|
|
10
|
+
let mockObjectQL: any;
|
|
11
11
|
|
|
12
12
|
beforeEach(() => {
|
|
13
13
|
// Mock Kernel
|
|
14
14
|
mockProtocol = {
|
|
15
15
|
saveMetaItem: vi.fn().mockResolvedValue({ success: true, message: 'Saved' }),
|
|
16
|
-
getMetaItem: vi.fn().mockResolvedValue({ success: true, item: { foo: 'bar' } })
|
|
16
|
+
getMetaItem: vi.fn().mockResolvedValue({ success: true, item: { foo: 'bar' } }),
|
|
17
|
+
findData: vi.fn().mockResolvedValue({ object: 'test', records: [], total: 0 }),
|
|
18
|
+
getData: vi.fn().mockResolvedValue({ object: 'test', id: '1', record: {} }),
|
|
17
19
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
mockObjectQL = {
|
|
22
|
+
insert: vi.fn().mockResolvedValue({ id: 'new_1' }),
|
|
23
|
+
find: vi.fn().mockResolvedValue([]),
|
|
24
|
+
update: vi.fn().mockResolvedValue({}),
|
|
25
|
+
delete: vi.fn().mockResolvedValue({}),
|
|
26
|
+
getObjects: vi.fn().mockReturnValue({}),
|
|
27
|
+
registry: {
|
|
28
|
+
getObject: vi.fn().mockReturnValue({ name: 'test_obj' }),
|
|
29
|
+
getRegisteredTypes: vi.fn().mockReturnValue([]),
|
|
30
|
+
getAllPackages: vi.fn().mockReturnValue([]),
|
|
31
|
+
},
|
|
21
32
|
};
|
|
22
33
|
|
|
23
34
|
kernel = {
|
|
24
|
-
broker: mockBroker,
|
|
25
35
|
context: {
|
|
26
36
|
getService: (name: string) => {
|
|
27
37
|
if (name === 'protocol') return mockProtocol;
|
|
38
|
+
if (name === 'objectql') return mockObjectQL;
|
|
28
39
|
return null;
|
|
29
40
|
}
|
|
30
41
|
}
|
|
@@ -55,11 +66,17 @@ describe('HttpDispatcher', () => {
|
|
|
55
66
|
});
|
|
56
67
|
});
|
|
57
68
|
|
|
58
|
-
it('should fallback to
|
|
59
|
-
// Mock protocol without saveMetaItem
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
it('should fallback to MetadataService when protocol is missing saveMetaItem', async () => {
|
|
70
|
+
// Mock protocol without saveMetaItem, but MetadataService with saveItem
|
|
71
|
+
const mockMetaSvc = {
|
|
72
|
+
saveItem: vi.fn().mockResolvedValue({ success: true, fromMetaSvc: true }),
|
|
73
|
+
};
|
|
74
|
+
(kernel as any).context.getService = (name: string) => {
|
|
75
|
+
if (name === 'protocol') return {};
|
|
76
|
+
if (name === 'metadata') return mockMetaSvc;
|
|
77
|
+
if (name === 'objectql') return mockObjectQL;
|
|
78
|
+
return null;
|
|
79
|
+
};
|
|
63
80
|
|
|
64
81
|
const context = { request: {} };
|
|
65
82
|
const body = { label: 'Fallback' };
|
|
@@ -68,12 +85,8 @@ describe('HttpDispatcher', () => {
|
|
|
68
85
|
const result = await dispatcher.handleMetadata(path, context, 'PUT', body);
|
|
69
86
|
|
|
70
87
|
expect(result.handled).toBe(true);
|
|
71
|
-
expect(
|
|
72
|
-
|
|
73
|
-
{ type: 'objects', name: 'my_obj', item: body },
|
|
74
|
-
{ request: context.request }
|
|
75
|
-
);
|
|
76
|
-
expect(result.response?.body?.data).toEqual({ success: true, fromBroker: true });
|
|
88
|
+
expect(mockMetaSvc.saveItem).toHaveBeenCalledWith('objects', 'my_obj', body);
|
|
89
|
+
expect(result.response?.body?.data).toEqual({ success: true, fromMetaSvc: true });
|
|
77
90
|
});
|
|
78
91
|
|
|
79
92
|
it('should return error if save fails', async () => {
|
|
@@ -90,18 +103,14 @@ describe('HttpDispatcher', () => {
|
|
|
90
103
|
expect(result.response?.body?.error?.message).toBe('Save failed');
|
|
91
104
|
});
|
|
92
105
|
|
|
93
|
-
it('should handle READ operations
|
|
94
|
-
|
|
106
|
+
it('should handle READ operations via ObjectQL registry', async () => {
|
|
107
|
+
mockObjectQL.registry.getObject.mockReturnValue({ name: 'my_obj', fields: {} });
|
|
95
108
|
|
|
96
109
|
const context = { request: {} };
|
|
97
110
|
const result = await dispatcher.handleMetadata('/objects/my_obj', context, 'GET');
|
|
98
111
|
|
|
99
112
|
expect(result.handled).toBe(true);
|
|
100
|
-
expect(
|
|
101
|
-
'metadata.getObject',
|
|
102
|
-
{ objectName: 'my_obj' },
|
|
103
|
-
{ request: context.request }
|
|
104
|
-
);
|
|
113
|
+
expect(mockObjectQL.registry.getObject).toHaveBeenCalledWith('my_obj');
|
|
105
114
|
});
|
|
106
115
|
});
|
|
107
116
|
|
|
@@ -287,13 +296,14 @@ describe('HttpDispatcher', () => {
|
|
|
287
296
|
expect(mockAuth.handler).toHaveBeenCalled();
|
|
288
297
|
});
|
|
289
298
|
|
|
290
|
-
it('should fallback to
|
|
299
|
+
it('should fallback to mock auth when async auth service has no handler', async () => {
|
|
291
300
|
(kernel as any).getService = vi.fn().mockResolvedValue({});
|
|
292
|
-
mockBroker.call.mockResolvedValue({ token: 'abc' });
|
|
293
301
|
|
|
294
|
-
const result = await dispatcher.handleAuth('/login', 'POST', {
|
|
302
|
+
const result = await dispatcher.handleAuth('/login', 'POST', { email: 'test@example.com' }, { request: {} });
|
|
295
303
|
expect(result.handled).toBe(true);
|
|
296
|
-
|
|
304
|
+
// Falls through to mock auth fallback (sign-in behavior)
|
|
305
|
+
expect(result.response?.status).toBe(200);
|
|
306
|
+
expect(result.response?.body?.user).toBeDefined();
|
|
297
307
|
});
|
|
298
308
|
|
|
299
309
|
it('should return unhandled when auth service not registered and no legacy match', async () => {
|
|
@@ -307,10 +317,9 @@ describe('HttpDispatcher', () => {
|
|
|
307
317
|
|
|
308
318
|
describe('handleAuth mock fallback (MSW/test mode)', () => {
|
|
309
319
|
beforeEach(() => {
|
|
310
|
-
// No auth service
|
|
320
|
+
// No auth service — simulates MSW/mock mode
|
|
311
321
|
(kernel as any).getService = vi.fn().mockResolvedValue(null);
|
|
312
322
|
(kernel as any).services = new Map();
|
|
313
|
-
(kernel as any).broker = null;
|
|
314
323
|
});
|
|
315
324
|
|
|
316
325
|
it('should mock sign-up/email endpoint', async () => {
|
|
@@ -344,7 +353,7 @@ describe('HttpDispatcher', () => {
|
|
|
344
353
|
expect(result.response?.body).toEqual({ success: true });
|
|
345
354
|
});
|
|
346
355
|
|
|
347
|
-
it('should mock login fallback when
|
|
356
|
+
it('should mock login fallback when no auth service registered', async () => {
|
|
348
357
|
const result = await dispatcher.handleAuth('/login', 'POST', { email: 'test@example.com' }, { request: {} });
|
|
349
358
|
expect(result.handled).toBe(true);
|
|
350
359
|
expect(result.response?.status).toBe(200);
|
|
@@ -449,17 +458,16 @@ describe('HttpDispatcher', () => {
|
|
|
449
458
|
expect(asyncProtocol.saveMetaItem).toHaveBeenCalled();
|
|
450
459
|
});
|
|
451
460
|
|
|
452
|
-
it('should fallback to
|
|
453
|
-
(kernel as any).context.getService = vi.fn().
|
|
454
|
-
|
|
461
|
+
it('should fallback to ObjectQL registry when async protocol returns null', async () => {
|
|
462
|
+
(kernel as any).context.getService = vi.fn().mockImplementation((name: string) => {
|
|
463
|
+
if (name === 'objectql') return mockObjectQL;
|
|
464
|
+
return null;
|
|
465
|
+
});
|
|
466
|
+
mockObjectQL.registry.getObject.mockReturnValue({ name: 'my_obj', fields: {} });
|
|
455
467
|
|
|
456
468
|
const result = await dispatcher.handleMetadata('/objects/my_obj', { request: {} }, 'GET');
|
|
457
469
|
expect(result.handled).toBe(true);
|
|
458
|
-
expect(
|
|
459
|
-
'metadata.getObject',
|
|
460
|
-
{ objectName: 'my_obj' },
|
|
461
|
-
{ request: {} }
|
|
462
|
-
);
|
|
470
|
+
expect(mockObjectQL.registry.getObject).toHaveBeenCalledWith('my_obj');
|
|
463
471
|
});
|
|
464
472
|
});
|
|
465
473
|
});
|
|
@@ -599,8 +607,8 @@ describe('HttpDispatcher', () => {
|
|
|
599
607
|
// ═══════════════════════════════════════════════════════════════
|
|
600
608
|
|
|
601
609
|
describe('handleData', () => {
|
|
602
|
-
it('should pass expand and select to
|
|
603
|
-
|
|
610
|
+
it('should pass expand and select to protocol for GET /data/:object/:id', async () => {
|
|
611
|
+
mockProtocol.getData.mockResolvedValue({ object: 'order_item', id: 'oi_1', record: { id: 'oi_1' } });
|
|
604
612
|
|
|
605
613
|
const result = await dispatcher.handleData(
|
|
606
614
|
'/order_item/oi_1', 'GET', {},
|
|
@@ -610,15 +618,13 @@ describe('HttpDispatcher', () => {
|
|
|
610
618
|
|
|
611
619
|
expect(result.handled).toBe(true);
|
|
612
620
|
expect(result.response?.status).toBe(200);
|
|
613
|
-
expect(
|
|
614
|
-
'
|
|
615
|
-
{ object: 'order_item', id: 'oi_1', expand: 'order,product', select: 'name,total' },
|
|
616
|
-
{ request: {} }
|
|
621
|
+
expect(mockProtocol.getData).toHaveBeenCalledWith(
|
|
622
|
+
{ object: 'order_item', id: 'oi_1', expand: 'order,product', select: 'name,total' }
|
|
617
623
|
);
|
|
618
624
|
});
|
|
619
625
|
|
|
620
626
|
it('should NOT pass non-allowlisted params for GET /data/:object/:id', async () => {
|
|
621
|
-
|
|
627
|
+
mockProtocol.getData.mockResolvedValue({ object: 'task', id: 't1', record: {} });
|
|
622
628
|
|
|
623
629
|
await dispatcher.handleData(
|
|
624
630
|
'/task/t1', 'GET', {},
|
|
@@ -627,15 +633,13 @@ describe('HttpDispatcher', () => {
|
|
|
627
633
|
);
|
|
628
634
|
|
|
629
635
|
// Only expand is passed; malicious and filter are dropped
|
|
630
|
-
expect(
|
|
631
|
-
'
|
|
632
|
-
{ object: 'task', id: 't1', expand: 'assignee' },
|
|
633
|
-
{ request: {} }
|
|
636
|
+
expect(mockProtocol.getData).toHaveBeenCalledWith(
|
|
637
|
+
{ object: 'task', id: 't1', expand: 'assignee' }
|
|
634
638
|
);
|
|
635
639
|
});
|
|
636
640
|
|
|
637
641
|
it('should pass full query (with expand/populate) for GET /data/:object list', async () => {
|
|
638
|
-
|
|
642
|
+
mockProtocol.findData.mockResolvedValue({ object: 'task', records: [], total: 0 });
|
|
639
643
|
|
|
640
644
|
const query = { populate: 'assignee,project', top: '10', skip: '0' };
|
|
641
645
|
const result = await dispatcher.handleData(
|
|
@@ -646,23 +650,19 @@ describe('HttpDispatcher', () => {
|
|
|
646
650
|
|
|
647
651
|
expect(result.handled).toBe(true);
|
|
648
652
|
// top → limit and skip → offset are normalized by the dispatcher
|
|
649
|
-
expect(
|
|
650
|
-
'
|
|
651
|
-
{ object: 'task', query: { populate: 'assignee,project', limit: '10', offset: '0' } },
|
|
652
|
-
{ request: {} }
|
|
653
|
+
expect(mockProtocol.findData).toHaveBeenCalledWith(
|
|
654
|
+
{ object: 'task', query: { populate: 'assignee,project', limit: '10', offset: '0' } }
|
|
653
655
|
);
|
|
654
656
|
});
|
|
655
657
|
|
|
656
658
|
it('should pass expand in query for GET /data/:object list', async () => {
|
|
657
|
-
|
|
659
|
+
mockProtocol.findData.mockResolvedValue({ object: 'order', records: [], total: 0 });
|
|
658
660
|
|
|
659
661
|
const query = { expand: 'customer,products' };
|
|
660
662
|
await dispatcher.handleData('/order', 'GET', {}, query, { request: {} });
|
|
661
663
|
|
|
662
|
-
expect(
|
|
663
|
-
'
|
|
664
|
-
{ object: 'order', query: { expand: 'customer,products' } },
|
|
665
|
-
{ request: {} }
|
|
664
|
+
expect(mockProtocol.findData).toHaveBeenCalledWith(
|
|
665
|
+
{ object: 'order', query: { expand: 'customer,products' } }
|
|
666
666
|
);
|
|
667
667
|
});
|
|
668
668
|
|
|
@@ -673,7 +673,7 @@ describe('HttpDispatcher', () => {
|
|
|
673
673
|
});
|
|
674
674
|
|
|
675
675
|
it('should handle POST /data/:object/query with body containing expand', async () => {
|
|
676
|
-
|
|
676
|
+
mockProtocol.findData.mockResolvedValue({ object: 'task', records: [] });
|
|
677
677
|
|
|
678
678
|
await dispatcher.handleData(
|
|
679
679
|
'/task/query', 'POST',
|
|
@@ -682,10 +682,8 @@ describe('HttpDispatcher', () => {
|
|
|
682
682
|
{ request: {} }
|
|
683
683
|
);
|
|
684
684
|
|
|
685
|
-
expect(
|
|
686
|
-
'
|
|
687
|
-
{ object: 'task', filter: { status: 'active' }, populate: ['assignee'] },
|
|
688
|
-
{ request: {} }
|
|
685
|
+
expect(mockProtocol.findData).toHaveBeenCalledWith(
|
|
686
|
+
{ object: 'task', query: { filter: { status: 'active' }, populate: ['assignee'] } }
|
|
689
687
|
);
|
|
690
688
|
});
|
|
691
689
|
});
|
|
@@ -774,7 +772,7 @@ describe('HttpDispatcher', () => {
|
|
|
774
772
|
expect(mockMetadata.revertPackage).toHaveBeenCalledWith('com.acme.crm');
|
|
775
773
|
});
|
|
776
774
|
|
|
777
|
-
it('should
|
|
775
|
+
it('should return 503 for publish when metadata service unavailable', async () => {
|
|
778
776
|
const mockRegistry = {
|
|
779
777
|
getAllPackages: vi.fn().mockReturnValue([]),
|
|
780
778
|
};
|
|
@@ -783,11 +781,10 @@ describe('HttpDispatcher', () => {
|
|
|
783
781
|
if (name === 'objectql') return Promise.resolve({ registry: mockRegistry });
|
|
784
782
|
return null;
|
|
785
783
|
});
|
|
786
|
-
mockBroker.call.mockResolvedValue({ success: true, packageId: 'crm', version: 1, publishedAt: '2025-01-01T00:00:00Z', itemsPublished: 2 });
|
|
787
784
|
|
|
788
785
|
const result = await dispatcher.handlePackages('/crm/publish', 'POST', {}, {}, { request: {} });
|
|
789
786
|
expect(result.handled).toBe(true);
|
|
790
|
-
expect(
|
|
787
|
+
expect(result.response?.status).toBe(503);
|
|
791
788
|
});
|
|
792
789
|
});
|
|
793
790
|
|
|
@@ -826,18 +823,27 @@ describe('HttpDispatcher', () => {
|
|
|
826
823
|
expect(result.response?.status).toBe(404);
|
|
827
824
|
});
|
|
828
825
|
|
|
829
|
-
it('should fallback to
|
|
830
|
-
|
|
831
|
-
|
|
826
|
+
it('should fallback to resolveService for getPublished when metadata service unavailable', async () => {
|
|
827
|
+
const metaSvc = {
|
|
828
|
+
getPublished: vi.fn().mockResolvedValue({ name: 'account', fields: ['name'] }),
|
|
829
|
+
};
|
|
830
|
+
(kernel as any).getService = vi.fn().mockImplementation((name: string) => {
|
|
831
|
+
if (name === 'metadata') return Promise.resolve(metaSvc);
|
|
832
|
+
if (name === 'objectql') return Promise.resolve(mockObjectQL);
|
|
833
|
+
return null;
|
|
834
|
+
});
|
|
835
|
+
(kernel as any).context = {
|
|
836
|
+
getService: (name: string) => {
|
|
837
|
+
if (name === 'metadata') return metaSvc;
|
|
838
|
+
if (name === 'objectql') return mockObjectQL;
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
832
842
|
|
|
833
843
|
const result = await dispatcher.handleMetadata('/object/account/published', { request: {} }, 'GET');
|
|
834
844
|
expect(result.handled).toBe(true);
|
|
835
845
|
expect(result.response?.status).toBe(200);
|
|
836
|
-
expect(
|
|
837
|
-
'metadata.getPublished',
|
|
838
|
-
{ type: 'object', name: 'account' },
|
|
839
|
-
{ request: {} }
|
|
840
|
-
);
|
|
846
|
+
expect(metaSvc.getPublished).toHaveBeenCalledWith('object', 'account');
|
|
841
847
|
});
|
|
842
848
|
});
|
|
843
849
|
|
|
@@ -1210,99 +1216,98 @@ describe('HttpDispatcher', () => {
|
|
|
1210
1216
|
});
|
|
1211
1217
|
});
|
|
1212
1218
|
|
|
1213
|
-
describe('handleMetadata
|
|
1214
|
-
let
|
|
1215
|
-
let
|
|
1219
|
+
describe('handleMetadata with minimal kernel (serverless/lightweight)', () => {
|
|
1220
|
+
let minimalKernel: any;
|
|
1221
|
+
let minimalDispatcher: HttpDispatcher;
|
|
1216
1222
|
|
|
1217
1223
|
beforeEach(() => {
|
|
1218
|
-
//
|
|
1224
|
+
// Minimal kernel — simulates a lightweight/serverless setup
|
|
1219
1225
|
// where only the protocol service and/or ObjectQL registry are available.
|
|
1220
|
-
|
|
1221
|
-
broker: null,
|
|
1226
|
+
minimalKernel = {
|
|
1222
1227
|
context: {
|
|
1223
1228
|
getService: vi.fn().mockReturnValue(null),
|
|
1224
1229
|
},
|
|
1225
1230
|
};
|
|
1226
|
-
|
|
1231
|
+
minimalDispatcher = new HttpDispatcher(minimalKernel);
|
|
1227
1232
|
});
|
|
1228
1233
|
|
|
1229
|
-
it('GET /meta should return default types
|
|
1234
|
+
it('GET /meta should return default types with minimal kernel', async () => {
|
|
1230
1235
|
const context = { request: {} };
|
|
1231
|
-
const result = await
|
|
1236
|
+
const result = await minimalDispatcher.handleMetadata('', context, 'GET');
|
|
1232
1237
|
expect(result.handled).toBe(true);
|
|
1233
1238
|
expect(result.response?.status).toBe(200);
|
|
1234
1239
|
expect(result.response?.body?.data?.types).toContain('object');
|
|
1235
1240
|
});
|
|
1236
1241
|
|
|
1237
|
-
it('GET /meta/types should return default types
|
|
1242
|
+
it('GET /meta/types should return default types with minimal kernel', async () => {
|
|
1238
1243
|
const context = { request: {} };
|
|
1239
|
-
const result = await
|
|
1244
|
+
const result = await minimalDispatcher.handleMetadata('/types', context, 'GET');
|
|
1240
1245
|
expect(result.handled).toBe(true);
|
|
1241
1246
|
expect(result.response?.status).toBe(200);
|
|
1242
1247
|
expect(result.response?.body?.data?.types).toContain('object');
|
|
1243
1248
|
});
|
|
1244
1249
|
|
|
1245
|
-
it('GET /meta/objects should use ObjectQL registry
|
|
1250
|
+
it('GET /meta/objects should use ObjectQL registry', async () => {
|
|
1246
1251
|
const mockRegistry = {
|
|
1247
1252
|
getAllObjects: vi.fn().mockReturnValue([{ name: 'account' }]),
|
|
1248
1253
|
getObject: vi.fn(),
|
|
1249
1254
|
};
|
|
1250
|
-
|
|
1255
|
+
minimalKernel.context.getService = vi.fn().mockImplementation((name: string) => {
|
|
1251
1256
|
if (name === 'objectql') return { registry: mockRegistry };
|
|
1252
1257
|
return null;
|
|
1253
1258
|
});
|
|
1254
1259
|
|
|
1255
1260
|
const context = { request: {} };
|
|
1256
|
-
const result = await
|
|
1261
|
+
const result = await minimalDispatcher.handleMetadata('/objects', context, 'GET');
|
|
1257
1262
|
expect(result.handled).toBe(true);
|
|
1258
1263
|
expect(result.response?.status).toBe(200);
|
|
1259
1264
|
expect(mockRegistry.getAllObjects).toHaveBeenCalled();
|
|
1260
1265
|
});
|
|
1261
1266
|
|
|
1262
|
-
it('GET /meta/objects/:name should use ObjectQL registry
|
|
1267
|
+
it('GET /meta/objects/:name should use ObjectQL registry', async () => {
|
|
1263
1268
|
const mockRegistry = {
|
|
1264
1269
|
registry: {
|
|
1265
1270
|
getObject: vi.fn().mockReturnValue({ name: 'account', fields: {} }),
|
|
1266
1271
|
},
|
|
1267
1272
|
};
|
|
1268
|
-
|
|
1273
|
+
minimalKernel.context.getService = vi.fn().mockImplementation((name: string) => {
|
|
1269
1274
|
if (name === 'objectql') return mockRegistry;
|
|
1270
1275
|
return null;
|
|
1271
1276
|
});
|
|
1272
1277
|
|
|
1273
1278
|
const context = { request: {} };
|
|
1274
|
-
const result = await
|
|
1279
|
+
const result = await minimalDispatcher.handleMetadata('/objects/account', context, 'GET');
|
|
1275
1280
|
expect(result.handled).toBe(true);
|
|
1276
1281
|
expect(result.response?.status).toBe(200);
|
|
1277
1282
|
expect(mockRegistry.registry.getObject).toHaveBeenCalledWith('account');
|
|
1278
1283
|
});
|
|
1279
1284
|
|
|
1280
|
-
it('GET /meta/:type/:name/published should return 404 when
|
|
1285
|
+
it('GET /meta/:type/:name/published should return 404 when metadata service is unavailable', async () => {
|
|
1281
1286
|
const context = { request: {} };
|
|
1282
|
-
const result = await
|
|
1287
|
+
const result = await minimalDispatcher.handleMetadata('/object/my_obj/published', context, 'GET');
|
|
1283
1288
|
expect(result.handled).toBe(true);
|
|
1284
1289
|
expect(result.response?.status).toBe(404);
|
|
1285
1290
|
});
|
|
1286
1291
|
|
|
1287
|
-
it('PUT /meta/:type/:name should return 501 when
|
|
1292
|
+
it('PUT /meta/:type/:name should return 501 when protocol is unavailable', async () => {
|
|
1288
1293
|
const context = { request: {} };
|
|
1289
1294
|
const body = { label: 'Test' };
|
|
1290
|
-
const result = await
|
|
1295
|
+
const result = await minimalDispatcher.handleMetadata('/objects/my_obj', context, 'PUT', body);
|
|
1291
1296
|
expect(result.handled).toBe(true);
|
|
1292
1297
|
expect(result.response?.status).toBe(501);
|
|
1293
1298
|
});
|
|
1294
1299
|
|
|
1295
|
-
it('should use protocol service
|
|
1300
|
+
it('should use protocol service with minimal kernel', async () => {
|
|
1296
1301
|
const mockProtocolLocal = {
|
|
1297
1302
|
getMetaTypes: vi.fn().mockResolvedValue({ types: ['custom_type'] }),
|
|
1298
1303
|
};
|
|
1299
|
-
|
|
1304
|
+
minimalKernel.context.getService = vi.fn().mockImplementation((name: string) => {
|
|
1300
1305
|
if (name === 'protocol') return mockProtocolLocal;
|
|
1301
1306
|
return null;
|
|
1302
1307
|
});
|
|
1303
1308
|
|
|
1304
1309
|
const context = { request: {} };
|
|
1305
|
-
const result = await
|
|
1310
|
+
const result = await minimalDispatcher.handleMetadata('/types', context, 'GET');
|
|
1306
1311
|
expect(result.handled).toBe(true);
|
|
1307
1312
|
expect(result.response?.status).toBe(200);
|
|
1308
1313
|
expect(mockProtocolLocal.getMetaTypes).toHaveBeenCalled();
|