fix(storage): neutral queryConstraints honored by both adapters
Bug: server adapter accepted queryConstraints (orderBy/limit) but ignored them.
Combat log [orderBy('timestamp','desc'), limit(500)] returned ALL logs unordered
in server mode — unbounded growth. No test caught it: shared contract tested
subscribeCollection with 0 constraints; the firebase-only queryConstraint test
never touched the server adapter. Parity gap.
Fix (Plan A — neutral shape):
- index.js: orderBy()/limit() now NEUTRAL builders returning {__type}. Removed
SDK orderBy/limit re-export.
- firebase.js: subscribeCollection translates neutral -> SDK query constraints.
- server.js: applyConstraints() helper sorts/limits client-side (backend returns
all). Constraints stored per collection, applied on initial fetch + WS change.
- contract.js: added 3 queryConstraint tests (orderBy desc, limit, no-constraints)
to SHARED contract — both adapters now run identical assertions.
- firebase.contract.test.js: removed now-redundant firebase-only constraint
block (covered by shared contract).
Data impact: ZERO. Constraints are query-time only, never stored. Combat log
docs keep timestamp field. No Firebase data migration needed.
208 tests green.
This commit is contained in:
@@ -232,6 +232,45 @@ function runStorageContract(name, factory) {
|
||||
expect(last).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
// queryConstraints: orderBy + limit. App's combat log uses
|
||||
// [orderBy('timestamp','desc'), limit(500)] — newest 500 entries.
|
||||
// Adapter MUST honor these. Constraint shape = neutral {__type} objects
|
||||
// (what firebase mock produces; what App passes via shared builders).
|
||||
describe('subscribeCollection queryConstraints', () => {
|
||||
const orderByC = (field, dir) => ({ __type: 'orderBy', field, dir });
|
||||
const limitC = (n) => ({ __type: 'limit', n });
|
||||
|
||||
beforeEach(async () => {
|
||||
// seed 5 log docs, timestamps out of order
|
||||
await storage.setDoc('logs/l1', { msg: 'one', timestamp: 1 });
|
||||
await storage.setDoc('logs/l2', { msg: 'two', timestamp: 3 });
|
||||
await storage.setDoc('logs/l3', { msg: 'three', timestamp: 5 });
|
||||
await storage.setDoc('logs/l4', { msg: 'four', timestamp: 2 });
|
||||
await storage.setDoc('logs/l5', { msg: 'five', timestamp: 4 });
|
||||
});
|
||||
|
||||
test('orderBy desc sorts newest-first', async () => {
|
||||
const result = await new Promise((resolve) => {
|
||||
storage.subscribeCollection('logs', resolve, [orderByC('timestamp', 'desc')]);
|
||||
});
|
||||
expect(result.map(d => d.msg)).toEqual(['three', 'five', 'two', 'four', 'one']);
|
||||
});
|
||||
|
||||
test('limit returns only first N after ordering', async () => {
|
||||
const result = await new Promise((resolve) => {
|
||||
storage.subscribeCollection('logs', resolve, [orderByC('timestamp', 'desc'), limitC(2)]);
|
||||
});
|
||||
expect(result.map(d => d.msg)).toEqual(['three', 'five']);
|
||||
});
|
||||
|
||||
test('no constraints returns all (insertion/id order)', async () => {
|
||||
const result = await new Promise((resolve) => {
|
||||
storage.subscribeCollection('logs', resolve, []);
|
||||
});
|
||||
expect(result).toHaveLength(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user