-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathclient.msw.test.ts
More file actions
212 lines (179 loc) · 9.22 KB
/
Copy pathclient.msw.test.ts
File metadata and controls
212 lines (179 loc) · 9.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { LiteKernel } from '@objectstack/core';
import { ObjectQLPlugin, SchemaRegistry } from '@objectstack/objectql';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { MSWPlugin } from '@objectstack/plugin-msw';
import { ObjectStackClient } from './index';
// 127.0.0.1 usage logic remains
const BASE_URL = 'http://127.0.0.1';
describe('ObjectStackClient (with MSW Plugin)', () => {
let kernel: LiteKernel;
let mswPlugin: MSWPlugin;
let server: any;
beforeAll(async () => {
// 1. Setup Kernel
kernel = new LiteKernel();
kernel.use(new ObjectQLPlugin());
// 2. Setup MSW Plugin (headless mode)
mswPlugin = new MSWPlugin({
enableBrowser: false,
// baseUrl: '/api/v1' // Default is /api/v1
});
kernel.use(mswPlugin);
// --- BROKER SHIM START ---
// HttpDispatcher requires a broker to function. We inject a simple shim.
(kernel as any).broker = {
call: async (action: string, params: any, opts: any) => {
const parts = action.split('.');
const service = parts[0];
const method = parts[1];
if (service === 'data') {
const ql = kernel.getService<any>('objectql');
if (method === 'create') {
const res = await ql.insert(params.object, params.data);
const record = { ...params.data, ...res };
return { object: params.object, id: record.id || record._id, record };
}
if (method === 'get') {
// Ensure we search by 'id' explicitly for InMemoryDriver
const record = await ql.findOne(params.object, { where: { id: params.id } });
return record ? { object: params.object, id: params.id, record } : null;
}
if (method === 'query') {
const queryOpts = params.query || {};
const records = await ql.find(params.object, { filter: queryOpts.filters || queryOpts.filter });
return { object: params.object, records, total: records.length };
}
if (method === 'find') {
const queryOpts = params.query || {};
const records = await ql.find(params.object, { filter: queryOpts.filters || queryOpts.filter });
return { object: params.object, records, total: records.length };
}
}
if (service === 'metadata') {
if (method === 'getObject') return SchemaRegistry.getObject(params.objectName);
if (method === 'objects') return SchemaRegistry.getAllObjects();
}
console.warn(`[BrokerShim] Action not implemented: ${action}`);
return null;
}
};
// --- BROKER SHIM END ---
await kernel.bootstrap();
// 3. Setup Driver & Schema
const ql = kernel.getService<any>('objectql');
ql.registerDriver(new InMemoryDriver(), true);
SchemaRegistry.registerObject({
name: 'customer',
fields: {
name: { type: 'text' }
}
});
// Add some data
await ql.insert('customer', { id: '101', name: 'Alice' });
await ql.insert('customer', { id: '102', name: 'Bob' });
// 4. Get handlers and start MSW Node Server
const handlers = mswPlugin.getHandlers();
// Manual handlers to cover gaps in MSWPlugin generation
handlers.push(
http.get('http://127.0.0.1/api/v1', () => {
return HttpResponse.json({
name: 'ObjectOS',
version: '1.0.0',
routes: {
data: '/api/v1/data',
metadata: '/api/v1/meta',
auth: '/api/v1/auth'
},
capabilities: ['data', 'metadata'],
features: {}
});
}),
http.get('http://127.0.0.1/api/v1/meta/object/:name', async ({ params }) => {
try {
const res = await (kernel as any).broker.call('metadata.getObject', { objectName: params.name });
return HttpResponse.json({ success: true, data: res });
} catch (e: any) { return HttpResponse.json({ success: false, error: e.message }, { status: 404 }); }
}),
http.get('http://127.0.0.1/api/v1/data/:object', async ({ params }) => {
try {
// Simplifying: ignoring query filters for this test
const res = await (kernel as any).broker.call('data.find', { object: params.object, filters: {} });
return HttpResponse.json({ success: true, data: res });
} catch (e: any) { return HttpResponse.json({ success: false, error: e.message }, { status: 500 }); }
}),
http.post('http://127.0.0.1/api/v1/data/:object', async ({ params, request }) => {
try {
const body = await request.json();
const res = await (kernel as any).broker.call('data.create', { object: params.object, data: body });
return HttpResponse.json({ success: true, data: res }, { status: 201 });
} catch (e: any) { return HttpResponse.json({ success: false, error: e.message }, { status: 500 }); }
}),
http.get('http://127.0.0.1/api/v1/data/:object/:id', async ({ params }) => {
try {
const res = await (kernel as any).broker.call('data.get', { object: params.object, id: params.id });
return HttpResponse.json({ success: true, data: res });
} catch (e: any) { return HttpResponse.json({ success: false, error: e.message }, { status: 404 }); }
})
);
server = setupServer(...handlers);
server.listen({ onUnhandledRequest: 'error' });
});
// Reset handlers after each test to ensure clean state
afterEach(() => server.resetHandlers());
afterAll(async () => {
if (server) server.close();
if (kernel) await kernel.shutdown();
});
it('should connect and discover endpoints', async () => {
// MSWPlugin configured with baseUrl: '' creates handlers at root.
// Client connects to /api/v1.
// To make them match in THIS test file where I used baseUrl: '',
// I should have configured MSWPlugin with baseUrl: '/api/v1' or left default.
// RE-FIXING SETUP in beforeAll (via edit).
// If I change MSWPlugin config to default ('/api/v1'),
// then Client(BASE_URL).connect() -> fetches BASE_URL/api/v1 -> matches MSW handler /api/v1.
const client = new ObjectStackClient({ baseUrl: BASE_URL });
await client.connect();
expect(client['discoveryInfo']).toBeDefined();
});
it('should fetch object metadata', async () => {
const client = new ObjectStackClient({ baseUrl: BASE_URL });
await client.connect();
// Spec: GetMetaItemResponse = { type, name, item }
const customerRes: any = await client.meta.getItem('object', 'customer');
expect(customerRes).toBeDefined();
// After unwrapResponse, we get the protocol-level response
// The manual handler wraps as { success, data: schema }, so unwrap yields the schema
const schema = customerRes.item || customerRes;
expect(schema.name).toBe('customer');
});
it('should find data records', async () => {
const client = new ObjectStackClient({ baseUrl: BASE_URL });
await client.connect();
// Spec: FindDataResponse = { object, records, total? }
const resultsRes = await client.data.find('customer');
expect(resultsRes.records).toBeDefined();
expect(resultsRes.records.length).toBe(2);
expect(resultsRes.records[0].name).toBe('Alice');
});
it('should get single record', async () => {
const client = new ObjectStackClient({ baseUrl: BASE_URL });
await client.connect();
// Spec: GetDataResponse = { object, id, record }
const recordRes = await client.data.get('customer', '101');
expect(recordRes.record).toBeDefined();
expect(recordRes.record.name).toBe('Alice');
});
it('should create record', async () => {
const client = new ObjectStackClient({ baseUrl: BASE_URL });
await client.connect();
// Spec: CreateDataResponse = { object, id, record }
const newRecordRes = await client.data.create('customer', { name: 'Charlie' });
expect(newRecordRes.record).toBeDefined();
expect(newRecordRes.record.name).toBe('Charlie');
expect(newRecordRes.id).toBeDefined();
});
});