@onchaindb/sdk 0.4.0 → 0.4.2
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/.DS_Store +0 -0
- package/.claude/settings.local.json +8 -0
- package/.gitignore +5 -0
- package/.idea/.gitignore +5 -0
- package/.idea/compiler.xml +6 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/sdk.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +257 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -3
- package/dist/client.js.map +1 -1
- package/dist/database.d.ts +0 -20
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +0 -40
- package/dist/database.js.map +1 -1
- package/dist/query-sdk/tests/setup.d.ts +16 -0
- package/dist/query-sdk/tests/setup.d.ts.map +1 -0
- package/dist/query-sdk/tests/setup.js +49 -0
- package/dist/query-sdk/tests/setup.js.map +1 -0
- package/examples/basic-usage.ts +136 -0
- package/examples/blob-upload-example.ts +140 -0
- package/examples/collection-schema-example.ts +304 -0
- package/examples/server-side-joins.ts +201 -0
- package/examples/tweet-self-joins-example.ts +352 -0
- package/package-lock.json +3823 -0
- package/package.json +1 -1
- package/skills.md +1096 -0
- package/src/.env +1 -0
- package/src/batch.d.ts +121 -0
- package/src/batch.js +205 -0
- package/src/batch.ts +257 -0
- package/src/client.ts +1856 -0
- package/src/database.d.ts +268 -0
- package/src/database.js +294 -0
- package/src/database.ts +695 -0
- package/src/index.d.ts +160 -0
- package/src/index.js +186 -0
- package/src/index.ts +253 -0
- package/src/query-sdk/ConditionBuilder.ts +103 -0
- package/src/query-sdk/FieldConditionBuilder.ts +2 -0
- package/src/query-sdk/NestedBuilders.ts +186 -0
- package/src/query-sdk/OnChainDB.ts +294 -0
- package/src/query-sdk/QueryBuilder.ts +1191 -0
- package/src/query-sdk/QueryResult.ts +375 -0
- package/src/query-sdk/README.md +866 -0
- package/src/query-sdk/SelectionBuilder.ts +94 -0
- package/src/query-sdk/adapters/HttpClientAdapter.ts +249 -0
- package/src/query-sdk/dist/ConditionBuilder.d.ts +22 -0
- package/src/query-sdk/dist/ConditionBuilder.js +90 -0
- package/src/query-sdk/dist/FieldConditionBuilder.d.ts +1 -0
- package/src/query-sdk/dist/FieldConditionBuilder.js +6 -0
- package/src/query-sdk/dist/NestedBuilders.d.ts +43 -0
- package/src/query-sdk/dist/NestedBuilders.js +144 -0
- package/src/query-sdk/dist/OnChainDB.d.ts +19 -0
- package/src/query-sdk/dist/OnChainDB.js +123 -0
- package/src/query-sdk/dist/QueryBuilder.d.ts +70 -0
- package/src/query-sdk/dist/QueryBuilder.js +295 -0
- package/src/query-sdk/dist/QueryResult.d.ts +52 -0
- package/src/query-sdk/dist/QueryResult.js +293 -0
- package/src/query-sdk/dist/SelectionBuilder.d.ts +20 -0
- package/src/query-sdk/dist/SelectionBuilder.js +80 -0
- package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +27 -0
- package/src/query-sdk/dist/adapters/HttpClientAdapter.js +170 -0
- package/src/query-sdk/dist/index.d.ts +36 -0
- package/src/query-sdk/dist/index.js +27 -0
- package/src/query-sdk/dist/operators.d.ts +56 -0
- package/src/query-sdk/dist/operators.js +289 -0
- package/src/query-sdk/dist/tests/setup.d.ts +15 -0
- package/src/query-sdk/dist/tests/setup.js +46 -0
- package/src/query-sdk/index.ts +59 -0
- package/src/query-sdk/jest.config.js +25 -0
- package/src/query-sdk/operators.ts +335 -0
- package/src/query-sdk/package.json +46 -0
- package/src/query-sdk/tests/FieldConditionBuilder.test.ts +84 -0
- package/src/query-sdk/tests/LogicalOperator.test.ts +85 -0
- package/src/query-sdk/tests/NestedBuilders.test.ts +321 -0
- package/src/query-sdk/tests/QueryBuilder.test.ts +348 -0
- package/src/query-sdk/tests/QueryResult.test.ts +464 -0
- package/src/query-sdk/tests/aggregations.test.ts +653 -0
- package/src/query-sdk/tests/comprehensive.test.ts +279 -0
- package/src/query-sdk/tests/integration.test.ts +608 -0
- package/src/query-sdk/tests/operators.test.ts +327 -0
- package/src/query-sdk/tests/setup.ts +59 -0
- package/src/query-sdk/tests/unit.test.ts +794 -0
- package/src/query-sdk/tsconfig.json +26 -0
- package/src/query-sdk/yarn.lock +3092 -0
- package/src/types.d.ts +131 -0
- package/src/types.js +46 -0
- package/src/types.ts +534 -0
- package/src/x402/index.ts +12 -0
- package/src/x402/types.ts +250 -0
- package/src/x402/utils.ts +332 -0
- package/tsconfig.json +20 -0
- package/yarn.lock +2309 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
// Integration tests for TypeScript SDK - Complete workflows
|
|
2
|
+
import {
|
|
3
|
+
QueryBuilder,
|
|
4
|
+
ConditionBuilder,
|
|
5
|
+
NestedConditionBuilder,
|
|
6
|
+
LogicalOperator,
|
|
7
|
+
FieldConditionBuilder,
|
|
8
|
+
QueryResult,
|
|
9
|
+
createQueryResult
|
|
10
|
+
} from '../index';
|
|
11
|
+
|
|
12
|
+
// Mock HTTP client that simulates real API responses
|
|
13
|
+
class MockHttpClient {
|
|
14
|
+
private responses: Record<string, any> = {
|
|
15
|
+
'/list': {
|
|
16
|
+
records: [
|
|
17
|
+
{
|
|
18
|
+
id: 1,
|
|
19
|
+
name: 'John Doe',
|
|
20
|
+
email: 'john@example.com',
|
|
21
|
+
age: 30,
|
|
22
|
+
department: 'Engineering',
|
|
23
|
+
user: {
|
|
24
|
+
profile: {
|
|
25
|
+
bio: 'Senior Developer',
|
|
26
|
+
settings: {
|
|
27
|
+
theme: 'dark',
|
|
28
|
+
notifications: true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
metadata: {
|
|
32
|
+
lastLogin: '2024-01-15T10:30:00Z',
|
|
33
|
+
ipAddress: '192.168.1.100'
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
scores: [95, 87, 92],
|
|
37
|
+
active: true,
|
|
38
|
+
location: { lat: 40.7128, lng: -74.0060 }
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 2,
|
|
42
|
+
name: 'Jane Smith',
|
|
43
|
+
email: 'jane@example.com',
|
|
44
|
+
age: 28,
|
|
45
|
+
department: 'Design',
|
|
46
|
+
user: {
|
|
47
|
+
profile: {
|
|
48
|
+
bio: 'UI/UX Designer',
|
|
49
|
+
settings: {
|
|
50
|
+
theme: 'light',
|
|
51
|
+
notifications: false
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
metadata: {
|
|
55
|
+
lastLogin: '2024-01-14T16:45:00Z',
|
|
56
|
+
ipAddress: '10.0.0.50'
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
scores: [88, 94, 90],
|
|
60
|
+
active: true,
|
|
61
|
+
location: { lat: 34.0522, lng: -118.2437 }
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 3,
|
|
65
|
+
name: 'Bob Johnson',
|
|
66
|
+
email: 'bob@example.com',
|
|
67
|
+
age: 35,
|
|
68
|
+
department: 'Engineering',
|
|
69
|
+
user: {
|
|
70
|
+
profile: {
|
|
71
|
+
bio: 'DevOps Engineer',
|
|
72
|
+
settings: {
|
|
73
|
+
theme: 'dark',
|
|
74
|
+
notifications: true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
metadata: {
|
|
78
|
+
lastLogin: '2024-01-13T09:15:00Z',
|
|
79
|
+
ipAddress: '192.168.1.101'
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
scores: [92, 85, 97],
|
|
83
|
+
active: false,
|
|
84
|
+
location: { lat: 41.8781, lng: -87.6298 }
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
total: 3,
|
|
88
|
+
page: 1,
|
|
89
|
+
limit: 10
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
async post(url: string, data: any, headers?: Record<string, string>) {
|
|
94
|
+
// Simulate network delay
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
96
|
+
|
|
97
|
+
const response = this.responses[url.split('http://localhost:3000')[1] || url];
|
|
98
|
+
return { data: response };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Test framework
|
|
103
|
+
interface IntegrationTest {
|
|
104
|
+
name: string;
|
|
105
|
+
test: () => Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class IntegrationTestRunner {
|
|
109
|
+
private tests: IntegrationTest[] = [];
|
|
110
|
+
private passed = 0;
|
|
111
|
+
private failed = 0;
|
|
112
|
+
|
|
113
|
+
add(name: string, test: () => Promise<void>) {
|
|
114
|
+
this.tests.push({ name, test });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async run() {
|
|
118
|
+
console.log(`🧪 Running ${this.tests.length} integration tests...\n`);
|
|
119
|
+
|
|
120
|
+
for (const testCase of this.tests) {
|
|
121
|
+
try {
|
|
122
|
+
await testCase.test();
|
|
123
|
+
console.log(`✅ ${testCase.name}`);
|
|
124
|
+
this.passed++;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(`❌ ${testCase.name}: ${error instanceof Error ? error.message : error}`);
|
|
127
|
+
this.failed++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(`\n📊 Integration Test Results: ${this.passed} passed, ${this.failed} failed`);
|
|
132
|
+
if (this.failed > 0) {
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Helper functions
|
|
139
|
+
function assertEqual<T>(actual: T, expected: T, message?: string) {
|
|
140
|
+
const actualStr = JSON.stringify(actual);
|
|
141
|
+
const expectedStr = JSON.stringify(expected);
|
|
142
|
+
if (actualStr !== expectedStr) {
|
|
143
|
+
throw new Error(`${message || 'Values not equal'}\nExpected: ${expectedStr}\nActual: ${actualStr}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function assertTrue(condition: boolean, message?: string) {
|
|
148
|
+
if (!condition) {
|
|
149
|
+
throw new Error(message || 'Condition is false');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Initialize test runner
|
|
154
|
+
const runner = new IntegrationTestRunner();
|
|
155
|
+
|
|
156
|
+
// ===== COMPLETE WORKFLOW TESTS =====
|
|
157
|
+
|
|
158
|
+
runner.add('Complete Query Workflow - Simple field query', async () => {
|
|
159
|
+
const client = new MockHttpClient();
|
|
160
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
161
|
+
|
|
162
|
+
const response = await queryBuilder
|
|
163
|
+
.collection('users')
|
|
164
|
+
.whereField('name').equals('John Doe')
|
|
165
|
+
.limit(10)
|
|
166
|
+
.selectFields(['id', 'name', 'email'])
|
|
167
|
+
.execute();
|
|
168
|
+
|
|
169
|
+
assertTrue(response.records.length > 0);
|
|
170
|
+
assertEqual(response.records[0].name, 'John Doe');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
runner.add('Complete Query Workflow - Nested field query with dot notation', async () => {
|
|
174
|
+
const client = new MockHttpClient();
|
|
175
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
176
|
+
|
|
177
|
+
const response = await queryBuilder
|
|
178
|
+
.collection('users')
|
|
179
|
+
.whereField('user.profile.bio').contains('Developer')
|
|
180
|
+
.execute();
|
|
181
|
+
|
|
182
|
+
assertTrue(response.records.length >= 1);
|
|
183
|
+
|
|
184
|
+
// Verify the raw query structure includes nested fields
|
|
185
|
+
const rawQuery = queryBuilder.buildRawQuery();
|
|
186
|
+
assertTrue(typeof rawQuery.find === 'object');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
runner.add('Complete Query Workflow - Complex logical conditions', async () => {
|
|
190
|
+
const client = new MockHttpClient();
|
|
191
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
192
|
+
|
|
193
|
+
const response = await queryBuilder
|
|
194
|
+
.collection('users')
|
|
195
|
+
.find(builder =>
|
|
196
|
+
builder.andGroup(() => [
|
|
197
|
+
LogicalOperator.Condition(new FieldConditionBuilder('age').greaterThan(25)),
|
|
198
|
+
LogicalOperator.Condition(new FieldConditionBuilder('department').equals('Engineering'))
|
|
199
|
+
])
|
|
200
|
+
)
|
|
201
|
+
.limit(5)
|
|
202
|
+
.execute();
|
|
203
|
+
|
|
204
|
+
assertTrue(response.records.length > 0);
|
|
205
|
+
assertEqual(response.limit, 5);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
runner.add('Complete Query Workflow - Nested builder with ORM-like syntax', async () => {
|
|
209
|
+
const client = new MockHttpClient();
|
|
210
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
211
|
+
|
|
212
|
+
const response = await queryBuilder
|
|
213
|
+
.collection('users')
|
|
214
|
+
.find(builder =>
|
|
215
|
+
builder.nested('user', nested =>
|
|
216
|
+
nested.andGroup(() => [
|
|
217
|
+
LogicalOperator.Condition(nested.field('profile').field('bio').contains('Engineer')),
|
|
218
|
+
LogicalOperator.Condition(nested.field('metadata').field('ipAddress').startsWith('192.168'))
|
|
219
|
+
])
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
.execute();
|
|
223
|
+
|
|
224
|
+
assertTrue(response.records.length >= 0);
|
|
225
|
+
|
|
226
|
+
// Verify the query structure
|
|
227
|
+
const rawQuery = queryBuilder.buildRawQuery();
|
|
228
|
+
assertTrue(typeof rawQuery.find === 'object');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
runner.add('Complete Query Workflow - Advanced operators', async () => {
|
|
232
|
+
const client = new MockHttpClient();
|
|
233
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
234
|
+
|
|
235
|
+
const response = await queryBuilder
|
|
236
|
+
.collection('users')
|
|
237
|
+
.whereField('age').between(25, 40)
|
|
238
|
+
.limit(10)
|
|
239
|
+
.execute();
|
|
240
|
+
|
|
241
|
+
assertTrue(response.records.length > 0);
|
|
242
|
+
|
|
243
|
+
// Test with date operators
|
|
244
|
+
const dateQuery = new QueryBuilder(client, 'http://localhost:3000', 'testApp')
|
|
245
|
+
.collection('users')
|
|
246
|
+
.whereField('user.metadata.lastLogin').dateAfter('2024-01-01')
|
|
247
|
+
.execute();
|
|
248
|
+
|
|
249
|
+
const dateResponse = await dateQuery;
|
|
250
|
+
assertTrue(dateResponse.records.length >= 0);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
runner.add('Complete Query Workflow - IP and geographical queries', async () => {
|
|
254
|
+
const client = new MockHttpClient();
|
|
255
|
+
|
|
256
|
+
// Test IP range query
|
|
257
|
+
const ipQuery = new QueryBuilder(client, 'http://localhost:3000', 'testApp')
|
|
258
|
+
.collection('users')
|
|
259
|
+
.whereField('user.metadata.ipAddress').ipInRange('192.168.1.0/24')
|
|
260
|
+
.execute();
|
|
261
|
+
|
|
262
|
+
const ipResponse = await ipQuery;
|
|
263
|
+
assertTrue(ipResponse.records.length >= 0);
|
|
264
|
+
|
|
265
|
+
// Test geographical query
|
|
266
|
+
const geoQuery = new QueryBuilder(client, 'http://localhost:3000', 'testApp')
|
|
267
|
+
.collection('users')
|
|
268
|
+
.whereField('location').geoWithinRadius(40.7128, -74.0060, 100)
|
|
269
|
+
.execute();
|
|
270
|
+
|
|
271
|
+
const geoResponse = await geoQuery;
|
|
272
|
+
assertTrue(geoResponse.records.length >= 0);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
runner.add('Complete Query Workflow - Array and type checking operators', async () => {
|
|
276
|
+
const client = new MockHttpClient();
|
|
277
|
+
|
|
278
|
+
// Test array operators
|
|
279
|
+
const arrayQuery = new QueryBuilder(client, 'http://localhost:3000', 'testApp')
|
|
280
|
+
.collection('users')
|
|
281
|
+
.whereField('scores').arrayLength(3)
|
|
282
|
+
.execute();
|
|
283
|
+
|
|
284
|
+
const arrayResponse = await arrayQuery;
|
|
285
|
+
assertTrue(arrayResponse.records.length >= 0);
|
|
286
|
+
|
|
287
|
+
// Test type checking
|
|
288
|
+
const typeQuery = new QueryBuilder(client, 'http://localhost:3000', 'testApp')
|
|
289
|
+
.collection('users')
|
|
290
|
+
.whereField('active').isBoolean()
|
|
291
|
+
.execute();
|
|
292
|
+
|
|
293
|
+
const typeResponse = await typeQuery;
|
|
294
|
+
assertTrue(typeResponse.records.length >= 0);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// ===== QUERY RESULT PROCESSING WORKFLOWS =====
|
|
298
|
+
|
|
299
|
+
runner.add('QueryResult Processing Workflow - Complete data analysis', async () => {
|
|
300
|
+
const client = new MockHttpClient();
|
|
301
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
302
|
+
|
|
303
|
+
const response = await queryBuilder
|
|
304
|
+
.collection('users')
|
|
305
|
+
.selectAll()
|
|
306
|
+
.execute();
|
|
307
|
+
|
|
308
|
+
const result = createQueryResult(response.records);
|
|
309
|
+
|
|
310
|
+
// Basic analysis
|
|
311
|
+
assertTrue(result.len() > 0);
|
|
312
|
+
assertTrue(!result.isEmpty());
|
|
313
|
+
|
|
314
|
+
// Pluck and analyze ages
|
|
315
|
+
const ages = result.pluckNumbers('age');
|
|
316
|
+
const ageSummary = result.summarizeNumeric('age');
|
|
317
|
+
|
|
318
|
+
assertTrue(ageSummary !== null);
|
|
319
|
+
assertTrue(ageSummary.mean > 0);
|
|
320
|
+
assertTrue(ageSummary.min <= ageSummary.max);
|
|
321
|
+
|
|
322
|
+
// Group by department
|
|
323
|
+
const byDepartment = result.groupBy('department');
|
|
324
|
+
assertTrue('Engineering' in byDepartment);
|
|
325
|
+
assertTrue('Design' in byDepartment);
|
|
326
|
+
|
|
327
|
+
// Sort by age
|
|
328
|
+
const sortedByAge = result.sortBy('age');
|
|
329
|
+
assertTrue(sortedByAge.length === result.len());
|
|
330
|
+
|
|
331
|
+
// Filter active users
|
|
332
|
+
const activeUsers = result.filter(user => user.active);
|
|
333
|
+
assertTrue(activeUsers.length >= 0);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
runner.add('QueryResult Processing Workflow - Nested field analysis', async () => {
|
|
337
|
+
const client = new MockHttpClient();
|
|
338
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
339
|
+
|
|
340
|
+
const response = await queryBuilder
|
|
341
|
+
.collection('users')
|
|
342
|
+
.selectAll()
|
|
343
|
+
.execute();
|
|
344
|
+
|
|
345
|
+
const result = createQueryResult(response.records);
|
|
346
|
+
|
|
347
|
+
// Analyze nested profile data
|
|
348
|
+
const themes = result.pluck('user.profile.settings.theme');
|
|
349
|
+
const themeGroups = result.groupBy('user.profile.settings.theme');
|
|
350
|
+
|
|
351
|
+
assertTrue(themes.length > 0);
|
|
352
|
+
assertTrue('dark' in themeGroups || 'light' in themeGroups);
|
|
353
|
+
|
|
354
|
+
// Sort by nested field
|
|
355
|
+
const sortedByBio = result.sortBy('user.profile.bio');
|
|
356
|
+
assertTrue(sortedByBio.length === result.len());
|
|
357
|
+
|
|
358
|
+
// Count by nested boolean
|
|
359
|
+
const notificationCounts = result.countBy('user.profile.settings.notifications');
|
|
360
|
+
assertTrue('true' in notificationCounts || 'false' in notificationCounts);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
runner.add('QueryResult Processing Workflow - Advanced aggregations', async () => {
|
|
364
|
+
const client = new MockHttpClient();
|
|
365
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
366
|
+
|
|
367
|
+
const response = await queryBuilder
|
|
368
|
+
.collection('users')
|
|
369
|
+
.selectAll()
|
|
370
|
+
.execute();
|
|
371
|
+
|
|
372
|
+
const result = createQueryResult(response.records);
|
|
373
|
+
|
|
374
|
+
// Multi-field sorting
|
|
375
|
+
const sortedMultiple = result.sortByMultiple([
|
|
376
|
+
{ field: 'department', ascending: true },
|
|
377
|
+
{ field: 'age', ascending: false }
|
|
378
|
+
]);
|
|
379
|
+
assertTrue(sortedMultiple.length === result.len());
|
|
380
|
+
|
|
381
|
+
// Pagination
|
|
382
|
+
const page1 = result.paginate(1, 2);
|
|
383
|
+
const page2 = result.paginate(2, 2);
|
|
384
|
+
assertTrue(page1.length <= 2);
|
|
385
|
+
assertTrue(page2.length >= 0);
|
|
386
|
+
|
|
387
|
+
// Chunking
|
|
388
|
+
const chunks = result.chunk(2);
|
|
389
|
+
assertTrue(chunks.length >= 1);
|
|
390
|
+
|
|
391
|
+
// Distinct values
|
|
392
|
+
const distinctDepts = result.distinctBy('department');
|
|
393
|
+
assertTrue(distinctDepts.length <= result.len());
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
runner.add('QueryResult Processing Workflow - Data export and joins', async () => {
|
|
397
|
+
const client = new MockHttpClient();
|
|
398
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
399
|
+
|
|
400
|
+
const response = await queryBuilder
|
|
401
|
+
.collection('users')
|
|
402
|
+
.selectAll()
|
|
403
|
+
.execute();
|
|
404
|
+
|
|
405
|
+
const users = createQueryResult(response.records);
|
|
406
|
+
|
|
407
|
+
// Create mock departments for join
|
|
408
|
+
const departments = createQueryResult([
|
|
409
|
+
{ name: 'Engineering', budget: 100000 },
|
|
410
|
+
{ name: 'Design', budget: 75000 }
|
|
411
|
+
]);
|
|
412
|
+
|
|
413
|
+
// Test join operations
|
|
414
|
+
const joinedData = users.innerJoin(departments, 'department', 'name');
|
|
415
|
+
assertTrue(joinedData.length >= 0);
|
|
416
|
+
|
|
417
|
+
// Test CSV export
|
|
418
|
+
const csv = users.toCsv();
|
|
419
|
+
const lines = csv.split('\n');
|
|
420
|
+
assertTrue(lines.length > 1); // At least header + 1 row
|
|
421
|
+
assertTrue(lines[0].includes('id'));
|
|
422
|
+
assertTrue(lines[0].includes('name'));
|
|
423
|
+
|
|
424
|
+
// Test JSON export
|
|
425
|
+
const json = users.toJSON();
|
|
426
|
+
const parsed = JSON.parse(json);
|
|
427
|
+
assertTrue(Array.isArray(parsed));
|
|
428
|
+
assertTrue(parsed.length > 0);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// ===== ERROR HANDLING AND EDGE CASE WORKFLOWS =====
|
|
432
|
+
|
|
433
|
+
runner.add('Error Handling Workflow - Missing HTTP client', async () => {
|
|
434
|
+
const queryBuilder = new QueryBuilder(); // No HTTP client
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
await queryBuilder
|
|
438
|
+
.collection('users')
|
|
439
|
+
.whereField('name').equals('John')
|
|
440
|
+
.execute();
|
|
441
|
+
|
|
442
|
+
throw new Error('Should have thrown error for missing HTTP client');
|
|
443
|
+
} catch (error) {
|
|
444
|
+
assertTrue(error instanceof Error);
|
|
445
|
+
assertTrue(error.message.includes('HTTP client'));
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
runner.add('Error Handling Workflow - Missing server URL', async () => {
|
|
450
|
+
const client = new MockHttpClient();
|
|
451
|
+
const queryBuilder = new QueryBuilder(client); // No server URL
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
await queryBuilder
|
|
455
|
+
.collection('users')
|
|
456
|
+
.whereField('name').equals('John')
|
|
457
|
+
.execute();
|
|
458
|
+
|
|
459
|
+
throw new Error('Should have thrown error for missing server URL');
|
|
460
|
+
} catch (error) {
|
|
461
|
+
assertTrue(error instanceof Error);
|
|
462
|
+
assertTrue(error.message.includes('Server URL'));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
runner.add('Edge Case Workflow - Empty result processing', async () => {
|
|
467
|
+
// Simulate empty response
|
|
468
|
+
class EmptyMockClient {
|
|
469
|
+
async post() {
|
|
470
|
+
return {
|
|
471
|
+
data: {
|
|
472
|
+
records: [],
|
|
473
|
+
total: 0,
|
|
474
|
+
page: 1,
|
|
475
|
+
limit: 10
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const client = new EmptyMockClient();
|
|
482
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
483
|
+
|
|
484
|
+
const response = await queryBuilder
|
|
485
|
+
.collection('users')
|
|
486
|
+
.whereField('nonexistent').equals('value')
|
|
487
|
+
.execute();
|
|
488
|
+
|
|
489
|
+
assertEqual(response.records.length, 0);
|
|
490
|
+
|
|
491
|
+
const result = createQueryResult(response.records);
|
|
492
|
+
assertTrue(result.isEmpty());
|
|
493
|
+
assertEqual(result.first(), undefined);
|
|
494
|
+
assertEqual(result.summarizeNumeric('age'), null);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
runner.add('Edge Case Workflow - Complex nested queries with missing fields', async () => {
|
|
498
|
+
const client = new MockHttpClient();
|
|
499
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
500
|
+
|
|
501
|
+
// Query with deeply nested field that might not exist
|
|
502
|
+
const response = await queryBuilder
|
|
503
|
+
.collection('users')
|
|
504
|
+
.find(builder =>
|
|
505
|
+
builder.nested('user', nested =>
|
|
506
|
+
nested.field('profile').field('settings').field('advanced').field('feature').equals('enabled')
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
.execute();
|
|
510
|
+
|
|
511
|
+
// Should not throw error even if nested field doesn't exist
|
|
512
|
+
assertTrue(response.records.length >= 0);
|
|
513
|
+
|
|
514
|
+
const result = createQueryResult(response.records);
|
|
515
|
+
|
|
516
|
+
// Test accessing missing nested fields
|
|
517
|
+
const missingValues = result.pluck('user.profile.settings.advanced.feature');
|
|
518
|
+
assertTrue(Array.isArray(missingValues));
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
runner.add('Performance Workflow - Large dataset simulation', async () => {
|
|
522
|
+
// Create a mock client that returns larger dataset
|
|
523
|
+
class LargeDataMockClient {
|
|
524
|
+
async post() {
|
|
525
|
+
const records = Array.from({ length: 100 }, (_, i) => ({
|
|
526
|
+
id: i + 1,
|
|
527
|
+
name: `User ${i + 1}`,
|
|
528
|
+
age: Math.floor(Math.random() * 50) + 20,
|
|
529
|
+
department: ['Engineering', 'Design', 'Marketing'][i % 3],
|
|
530
|
+
user: {
|
|
531
|
+
profile: {
|
|
532
|
+
bio: `Bio for user ${i + 1}`,
|
|
533
|
+
settings: {
|
|
534
|
+
theme: ['dark', 'light'][i % 2],
|
|
535
|
+
notifications: i % 2 === 0
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
scores: [
|
|
540
|
+
Math.floor(Math.random() * 100),
|
|
541
|
+
Math.floor(Math.random() * 100),
|
|
542
|
+
Math.floor(Math.random() * 100)
|
|
543
|
+
],
|
|
544
|
+
active: i % 3 !== 0
|
|
545
|
+
}));
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
data: {
|
|
549
|
+
records,
|
|
550
|
+
total: 1000,
|
|
551
|
+
page: 1,
|
|
552
|
+
limit: 100
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const client = new LargeDataMockClient();
|
|
559
|
+
const queryBuilder = new QueryBuilder(client, 'http://localhost:3000', 'testApp');
|
|
560
|
+
|
|
561
|
+
const startTime = Date.now();
|
|
562
|
+
|
|
563
|
+
const response = await queryBuilder
|
|
564
|
+
.collection('users')
|
|
565
|
+
.find(builder =>
|
|
566
|
+
builder.andGroup(() => [
|
|
567
|
+
LogicalOperator.Condition(new FieldConditionBuilder('age').between(25, 45)),
|
|
568
|
+
LogicalOperator.Condition(new FieldConditionBuilder('active').isTrue())
|
|
569
|
+
])
|
|
570
|
+
)
|
|
571
|
+
.limit(50)
|
|
572
|
+
.execute();
|
|
573
|
+
|
|
574
|
+
const queryTime = Date.now() - startTime;
|
|
575
|
+
|
|
576
|
+
const result = createQueryResult(response.records);
|
|
577
|
+
|
|
578
|
+
const processingStartTime = Date.now();
|
|
579
|
+
|
|
580
|
+
// Perform various operations on the large dataset
|
|
581
|
+
const summary = result.summarizeNumeric('age');
|
|
582
|
+
const grouped = result.groupBy('department');
|
|
583
|
+
const sorted = result.sortByMultiple([
|
|
584
|
+
{ field: 'department', ascending: true },
|
|
585
|
+
{ field: 'age', ascending: false }
|
|
586
|
+
]);
|
|
587
|
+
const filtered = result.filter(user => user.active);
|
|
588
|
+
const csv = result.toCsv();
|
|
589
|
+
|
|
590
|
+
const processingTime = Date.now() - processingStartTime;
|
|
591
|
+
|
|
592
|
+
console.log(` Query time: ${queryTime}ms, Processing time: ${processingTime}ms`);
|
|
593
|
+
|
|
594
|
+
// Verify results
|
|
595
|
+
assertTrue(response.records.length > 0);
|
|
596
|
+
assertTrue(summary !== null);
|
|
597
|
+
assertTrue(Object.keys(grouped).length > 0);
|
|
598
|
+
assertTrue(sorted.length > 0);
|
|
599
|
+
assertTrue(filtered.length >= 0);
|
|
600
|
+
assertTrue(csv.length > 0);
|
|
601
|
+
|
|
602
|
+
// Performance should be reasonable (adjust thresholds as needed)
|
|
603
|
+
assertTrue(queryTime < 1000, 'Query should complete in reasonable time');
|
|
604
|
+
assertTrue(processingTime < 1000, 'Processing should complete in reasonable time');
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// Run all integration tests
|
|
608
|
+
runner.run();
|