diff --git a/src/app.test.ts b/src/app.test.ts index bac286d98..f02d4a9f4 100644 --- a/src/app.test.ts +++ b/src/app.test.ts @@ -1,4 +1,4 @@ - import assert from 'node:assert/strict' +import assert from 'node:assert/strict' import { writeFileSync } from 'node:fs' import { join } from 'node:path' import test from 'node:test' @@ -139,14 +139,30 @@ await test('createApp', async (t) => { await t.test('GET /posts?_where=... overrides query params', async () => { const where = encodeURIComponent(JSON.stringify({ title: { eq: 'foo' } })) - const response = await fetch( - `http://localhost:${port}/posts?title:eq=bar&_where=${where}`, - ) + const response = await fetch(`http://localhost:${port}/posts?title:eq=bar&_where=${where}`) assert.equal(response.status, 200) const data = await response.json() assert.deepEqual(data, [{ id: '1', title: 'foo' }]) }) + await t.test('GET /posts?_where=... rejects missing nested fields', async () => { + db.data = { + posts: [ + { id: '1', title: 'a' }, + { id: '2', title: 'b' }, + ], + comments: [{ id: '1', postId: '1' }], + object: { f1: 'foo' }, + } + + const where = encodeURIComponent(JSON.stringify({ title: { nested: { eq: 'zzz' } } })) + const response = await fetch(`http://localhost:${port}/posts?_where=${where}`) + + assert.equal(response.status, 200) + const data = await response.json() + assert.deepEqual(data, []) + }) + await t.test('POST /posts with array body returns 400', async () => { const response = await fetch(`http://localhost:${port}/posts`, { method: 'POST', diff --git a/src/matches-where.test.ts b/src/matches-where.test.ts index 26a2be4a9..612f2813f 100644 --- a/src/matches-where.test.ts +++ b/src/matches-where.test.ts @@ -26,6 +26,10 @@ await test('matchesWhere', async (t) => { [{ or: [{ a: { lt: 0 } }, { b: { gt: 20 } }] }, false], [{ nested: { a: { eq: 10 } } }, true], [{ nested: { b: { lt: 20 } } }, false], + [{ missing: { a: { eq: 10 } } }, false], + [{ a: { nested: { eq: 10 } } }, false], + [{ or: [{ missing: { a: { eq: 10 } } }, { nested: { a: { eq: 11 } } }] }, false], + [{ or: [{ missing: { a: { eq: 10 } } }, { nested: { a: { eq: 10 } } }] }, true], [{ a: { in: [10, 11] } }, true], [{ a: { in: [1, 2] } }, false], [{ c: { in: ['x', 'y'] } }, true], diff --git a/src/matches-where.ts b/src/matches-where.ts index 036b00c69..64ccc4854 100644 --- a/src/matches-where.ts +++ b/src/matches-where.ts @@ -21,6 +21,13 @@ function getKnownOperators(value: unknown): WhereOperator[] { return ops } +function hasKnownOperator(value: unknown): boolean { + if (!isJSONObject(value)) return false + if (getKnownOperators(value).length > 0) return true + + return Object.values(value).some((item) => hasKnownOperator(item)) +} + export function matchesWhere(obj: JsonObject, where: JsonObject): boolean { for (const [key, value] of Object.entries(where)) { if (key === 'or') { @@ -72,9 +79,11 @@ export function matchesWhere(obj: JsonObject, where: JsonObject): boolean { continue } - if (isJSONObject(field)) { - if (!matchesWhere(field, value)) return false + if (!isJSONObject(field)) { + if (hasKnownOperator(value)) return false + continue } + if (!matchesWhere(field, value)) return false continue }