/** * Where-clause operator correctness (8.0.12) — the three query-layer fixes: * * 1. Unknown operators fail LOUD. A typo like `{ role: { notIn: [...] } }` or * any gibberish operator now throws BrainyError('INVALID_QUERY') instead of * silently matching nothing (the invisible-degrade class). Validated up * front in find(), so it throws even when the result set is empty. * 2. Documented operators the in-memory matcher was missing now work: `in` * (alias for oneOf) and the long-form comparison aliases * `greaterThanOrEqual` / `lessThanOrEqual`. * 3. A user metadata field named `level` is indexable + queryable — it was * silently dropped by an over-broad HNSW-internal skip-list, so * `where: { level: ... }` returned nothing. */ import { describe, it, expect, afterEach } from 'vitest' import { Brainy, NounType, BrainyError } from '../../src/index.js' const brains: any[] = [] async function makeBrain(): Promise { const brain: any = new Brainy({ storage: { type: 'memory' }, requireSubtype: false, silent: true, plugins: [] }) await brain.init() brains.push(brain) return brain } afterEach(async () => { for (const b of brains.splice(0)) await b.close().catch(() => {}) }) const V = () => Array.from({ length: 384 }, () => Math.random()) describe('Where-operator validation + documented-operator coverage (8.0.12)', () => { it('unknown operators throw INVALID_QUERY instead of silently returning [] (item 3)', async () => { const brain = await makeBrain() for (const role of ['staff', 'customer', 'vendor']) { await brain.add({ data: role, type: NounType.Person, metadata: { role }, vector: V() }) } // notIn is not an operator (use ne / not+in). It must THROW, not match nothing. await expect(brain.find({ type: NounType.Person, where: { role: { notIn: ['staff'] } } })) .rejects.toBeInstanceOf(BrainyError) await expect(brain.find({ type: NounType.Person, where: { role: { notIn: ['staff'] } } })) .rejects.toMatchObject({ type: 'INVALID_QUERY' }) // Gibberish likewise. await expect(brain.find({ where: { role: { blorp: 1 } } })).rejects.toMatchObject({ type: 'INVALID_QUERY' }) // Nested inside a logical operator is still validated. await expect(brain.find({ where: { not: { role: { nope: 1 } } } })).rejects.toMatchObject({ type: 'INVALID_QUERY' }) await expect(brain.find({ where: { anyOf: [{ role: { xx: 1 } }] } })).rejects.toMatchObject({ type: 'INVALID_QUERY' }) }) it('the documented `in` alias and long-form comparison aliases work (item 2)', async () => { const brain = await makeBrain() for (let i = 0; i < 9; i++) { await brain.add({ data: 'n' + i, type: NounType.Person, metadata: { role: ['staff', 'customer', 'vendor'][i % 3], score: i }, vector: V() }) } await brain.flush() // `in` === oneOf expect((await brain.find({ type: NounType.Person, where: { role: { in: ['customer', 'vendor'] } } })).length).toBe(6) expect((await brain.find({ type: NounType.Person, where: { not: { role: { in: ['staff'] } } } })).length).toBe(6) // long-form comparison aliases (were silently unhandled by the in-memory matcher) expect((await brain.find({ type: NounType.Person, where: { score: { greaterThanOrEqual: 6 } } })).length).toBe(3) // 6,7,8 expect((await brain.find({ type: NounType.Person, where: { score: { lessThanOrEqual: 2 } } })).length).toBe(3) // 0,1,2 }) it('a user metadata field named "level" is indexable + queryable (item: HNSW field-name collision)', async () => { const brain = await makeBrain() for (let i = 0; i < 9; i++) { await brain.add({ data: 'entity ' + i, type: NounType.Person, metadata: { level: i }, vector: V() }) } await brain.flush() expect((await brain.find({ type: NounType.Person, where: { level: 4 } })).length).toBe(1) // exact expect((await brain.find({ type: NounType.Person, where: { level: { gt: 4 } } })).length).toBe(4) // 5..8 expect((await brain.find({ type: NounType.Person, where: { level: { exists: true } } })).length).toBe(9) }) it('valid operators are unaffected — control', async () => { const brain = await makeBrain() for (const s of ['a', 'b', 'c']) await brain.add({ data: s, type: NounType.Concept, metadata: { tag: s }, vector: V() }) await brain.flush() expect((await brain.find({ type: NounType.Concept, where: { tag: { oneOf: ['a', 'b'] } } })).length).toBe(2) expect((await brain.find({ type: NounType.Concept, where: { tag: 'c' } })).length).toBe(1) expect((await brain.find({ type: NounType.Concept, where: { tag: { exists: true } } })).length).toBe(3) }) })