Skip to content

Commit f8bf528

Browse files
Copilothotlong
andcommitted
Fix CI test failures: pagination and bulk operations
- Fixed pagination metadata calculation to exclude limit/skip from count - Fixed MockDriver to support QueryAST 'top' field instead of 'limit' - Fixed MockDriver to handle FilterNode array format from QueryAST - Fixed count method to exclude skip/limit parameters - All 129 tests now passing Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 1220f16 commit f8bf528

3 files changed

Lines changed: 83 additions & 26 deletions

File tree

packages/runtime/server/src/server.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,11 @@ export class ObjectQLServer {
182182
const skip = args.skip || 0;
183183
const limit = args.limit || items.length;
184184

185-
// Get total count - use the same arguments as the query to ensure consistency
186-
const total = await repo.count(args || {});
185+
// Get total count - exclude limit/skip to count all matching records
186+
const countArgs: any = {};
187+
if (args.filters) countArgs.filters = args.filters;
188+
if (args.expand) countArgs.expand = args.expand;
189+
const total = await repo.count(countArgs);
187190

188191
const size = limit;
189192
const page = limit > 0 ? Math.floor(skip / limit) + 1 : 1;

packages/runtime/server/test/rest-advanced.test.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ class MockDriver implements Driver {
4141
}
4242
}
4343

44-
// Apply skip and limit
44+
// Apply skip and top/limit (QueryAST uses 'top' for limit)
4545
if (query?.skip) {
4646
items = items.slice(query.skip);
4747
}
48-
if (query?.limit) {
49-
items = items.slice(0, query.limit);
48+
if (query?.top || query?.limit) {
49+
items = items.slice(0, query.top || query.limit);
5050
}
5151

5252
return items;
@@ -87,7 +87,12 @@ class MockDriver implements Driver {
8787
}
8888

8989
async count(objectName: string, query: any) {
90-
const items = await this.find(objectName, query);
90+
// Count should not apply skip/limit, only filters
91+
const countQuery = { ...query };
92+
delete countQuery.skip;
93+
delete countQuery.top;
94+
delete countQuery.limit;
95+
const items = await this.find(objectName, countQuery);
9196
return items.length;
9297
}
9398

packages/runtime/server/test/rest.test.ts

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,37 +27,80 @@ class MockDriver implements Driver {
2727
async find(objectName: string, query: any) {
2828
let items = this.data[objectName] || [];
2929

30-
// Apply filters if provided
30+
// Apply filters if provided (supports FilterNode array format)
3131
if (query && query.filters) {
32-
const filters = query.filters;
33-
if (typeof filters === 'object') {
34-
const filterKeys = Object.keys(filters);
35-
if (filterKeys.length > 0) {
36-
items = items.filter(item => {
37-
for (const [key, value] of Object.entries(filters)) {
38-
if (item[key] !== value) {
39-
return false;
40-
}
41-
}
42-
return true;
43-
});
44-
}
45-
}
32+
items = items.filter(item => this.matchesFilter(item, query.filters));
4633
}
4734

48-
// Apply skip and limit if provided
35+
// Apply skip and top/limit if provided (QueryAST uses 'top' for limit)
4936
if (query) {
5037
if (query.skip) {
5138
items = items.slice(query.skip);
5239
}
53-
if (query.limit) {
54-
items = items.slice(0, query.limit);
40+
if (query.top || query.limit) {
41+
items = items.slice(0, query.top || query.limit);
5542
}
5643
}
5744

5845
return items;
5946
}
60-
47+
48+
private matchesFilter(item: any, filter: any): boolean {
49+
if (!filter) return true;
50+
51+
// Handle FilterNode array format: [[field, op, value]] or [[field, op, value], 'and', [field2, op2, value2]]
52+
if (Array.isArray(filter)) {
53+
// Single condition: [field, op, value]
54+
if (filter.length === 3 && typeof filter[0] === 'string') {
55+
const [field, op, value] = filter;
56+
return this.evaluateCondition(item, field, op, value);
57+
}
58+
59+
// Multiple conditions with logical operators
60+
let result = true;
61+
let currentOp = 'and';
62+
for (let i = 0; i < filter.length; i++) {
63+
const element = filter[i];
64+
if (typeof element === 'string') {
65+
currentOp = element; // 'and' or 'or'
66+
} else if (Array.isArray(element)) {
67+
const conditionResult = this.matchesFilter(item, element);
68+
if (currentOp === 'and') {
69+
result = result && conditionResult;
70+
} else if (currentOp === 'or') {
71+
result = result || conditionResult;
72+
}
73+
}
74+
}
75+
return result;
76+
}
77+
78+
// Handle simple object format: { field: value }
79+
if (typeof filter === 'object') {
80+
for (const [key, value] of Object.entries(filter)) {
81+
if (item[key] !== value) {
82+
return false;
83+
}
84+
}
85+
return true;
86+
}
87+
88+
return true;
89+
}
90+
91+
private evaluateCondition(item: any, field: string, op: string, value: any): boolean {
92+
const fieldValue = item[field];
93+
switch (op) {
94+
case '=': return fieldValue === value;
95+
case '!=': return fieldValue !== value;
96+
case '>': return fieldValue > value;
97+
case '>=': return fieldValue >= value;
98+
case '<': return fieldValue < value;
99+
case '<=': return fieldValue <= value;
100+
case 'in': return Array.isArray(value) && value.includes(fieldValue);
101+
default: return true;
102+
}
103+
}
61104
async findOne(objectName: string, id: string | number, query?: any, options?: any) {
62105
const items = this.data[objectName] || [];
63106
if (id !== undefined && id !== null) {
@@ -97,7 +140,13 @@ class MockDriver implements Driver {
97140
}
98141

99142
async count(objectName: string, query: any) {
100-
return (this.data[objectName] || []).length;
143+
// Count should apply filters but not skip/limit
144+
const countQuery = { ...query };
145+
delete countQuery.skip;
146+
delete countQuery.top;
147+
delete countQuery.limit;
148+
const items = await this.find(objectName, countQuery);
149+
return items.length;
101150
}
102151

103152
async createMany(objectName: string, data: any[]) {

0 commit comments

Comments
 (0)