UNPKG

@travetto/model-query

Version:

Datastore abstraction for advanced query support.

509 lines (422 loc) 11.5 kB
import assert from 'node:assert'; import { Suite, Test } from '@travetto/test'; import { TimeUtil } from '@travetto/runtime'; import { NotFoundError, ModelCrudSupport } from '@travetto/model'; import { BaseModelSuite } from '@travetto/model/support/test/base.ts'; import { Aged, Location, Names, Note, Person, SimpleList, WithNestedLists, WithNestedNestedLists } from './model.ts'; import { ModelQuerySupport } from '../../src/types/query.ts'; @Suite() export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport & ModelCrudSupport> { supportsGeo = true; @Test() async testIds() { const svc = await this.service; const people = [1, 2, 3, 8].map(x => Person.from({ id: svc.idSource.create(), name: 'Bob Omber', age: 20 + x, gender: 'm', address: { street1: 'a', ...(x === 1 ? { street2: 'b' } : {}) } })); await this.saveAll(Person, people); const one = await svc.queryOne(Person, { where: { id: people[0].id } }); assert(one.id === people[0].id); const one2 = await svc.queryOne(Person, { where: { id: { $eq: people[0].id } } }); assert(one2.id === people[0].id); const none = await svc.queryCount(Person, { where: { id: { $ne: people[0].id } } }); assert(none === 3); const noneIds = await svc.query(Person, { where: { id: { $ne: people[0].id } } }); assert(noneIds.every(x => x.id !== people[0].id)); } @Test('verify word boundary') async testWordBoundary() { const service = await this.service; await this.saveAll(Person, [1, 2, 3, 8].map(x => Person.from({ name: 'Bob Omber', age: 20 + x, gender: 'm', address: { street1: 'a', ...(x === 1 ? { street2: 'b' } : {}) } }))); const results = await service.query(Person, { where: { name: { $regex: /\bomb.*/i } } }); assert(results.length === 4); const results2 = await service.query(Person, { where: { name: { $regex: /\bmbo.*/i } } }); assert(results2.length === 0); const results3 = await service.query(Person, { where: { name: { $regex: /\bomb.*/ } } }); assert(results3.length === 0); } @Test('verify query one behavior') async testQueryOne() { const service = await this.service; await this.saveAll(Person, [1, 2, 3, 8].map(x => Person.from({ name: 'Bob Omber', age: 20 + x, gender: 'm', address: { street1: 'a', ...(x === 1 ? { street2: 'b' } : {}) } }))); await assert.rejects(() => service.queryOne(Person, { where: { gender: 'm' } }), /Invalid number of results/); await assert.rejects(() => service.queryOne(Person, { where: { gender: 'z' } }), NotFoundError); await assert.rejects(() => service.queryOne(Person, { where: { gender: 'z' } }), /No results found for query/); await assert.rejects(() => service.queryOne(Person, { where: { id: 'orange' } }), NotFoundError); await assert.rejects(() => service.queryOne(Person, { where: { id: 'orange' } }), /orange/); } @Test('Verify array $in queries work properly') async testArrayContains() { const svc = await this.service; const first = await svc.create(SimpleList, SimpleList.from({ names: ['a', 'b', 'c'] })); const second = await svc.create(SimpleList, SimpleList.from({ names: ['b', 'c', 'd'] })); const single = await svc.query(SimpleList, { where: { names: { $in: ['a'] } } }); assert(single.length === 1); const multi = await svc.query(SimpleList, { where: { names: { $in: ['a', 'd'] } } }); assert(multi.length === 2); const multiIntersect = await svc.query(SimpleList, { where: { names: { $in: ['b', 'c'] } } }); assert(multiIntersect.length === 2); const none = await svc.query(SimpleList, { where: { names: { $in: ['z', 'w'] } } }); assert(none.length === 0); const ids = await svc.query(SimpleList, { where: { id: { $in: [first.id, second.id] } } }); assert(ids.length === 2); const idNin = await svc.query(SimpleList, { where: { id: { $nin: ['a', 'b'] } } }); assert(idNin.length === 2); } @Test('verify all operators') async testArrayAll() { const service = await this.service; await this.saveAll(Names, [ Names.from({ values: ['a', 'b', 'c'] }), Names.from({ values: ['a', 'b'] }), Names.from({ values: ['b', 'c'] }), ]); const results = await service.query(Names, {}); assert(results.length === 3); const results2 = await service.query(Names, { where: { values: { $all: ['a', 'b', 'c'] } } }); assert(results2.length === 1); const results3 = await service.query(Names, { where: { values: { $all: ['a', 'b'] } } }); assert(results3.length === 2); const results4 = await service.query(Names, { where: { values: { $all: ['a', 'a', 'b'] } } }); assert(results4.length === 2); const results5 = await service.query(Names, { where: { values: { $all: ['b', 'b', 'b'] } } }); assert(results5.length === 3); } @Test('verify sorting') async testSorting() { const service = await this.service; const people = [1, 2, 3, 8].map(x => Person.from({ id: service.idSource.create(), name: 'Bob', age: 20 + x, gender: 'm', address: { street1: 'a', ...(x === 1 ? { street2: 'b' } : {}) } })); await this.saveAll(Person, people); const all = await service.query(Person, { sort: [{ age: -1 }] }); assert(all[0].age >= all[1].age); } @Test('Test within') async testWithin() { if (!this.supportsGeo) { return; } const svc = await this.service; const toAdd = []; for (let i = 0; i < 5; i++) { for (let j = 0; j < 5; j++) { toAdd.push(Location.from({ point: [i, j], })); } } await this.saveAll(Location, toAdd); assert(await svc.queryCount(Location, {}) === 25); const result = await svc.query(Location, { limit: 100, where: { point: { $geoWithin: [[-1, -1], [-1, 6], [6, 6], [6, -1]] } } }); assert(result.length === 25); const rad = await svc.query(Location, { limit: 100, where: { point: { $near: [3, 3], $maxDistance: 100, $unit: 'km' } } }); assert(rad.length < 25); assert(rad.length > 0); } @Test() async verifyNestedQuery() { const service = await this.service; const id = service.idSource.create(); await service.create(Note, Note.from({ id, entities: [ { label: 'hi', id } ] })); const out = await service.queryCount(Note, { where: { entities: { id } } }); assert(out === 1); const out2 = await service.query(Note, { where: { entities: { id } } }); assert(out2.length === 1); } @Test() async verifyDateRange() { const service = await this.service; await this.saveAll(Aged, (['-5d', '-4d', '-3d', '-2d', '-1d', '0d', '1d', '2d', '3d', '4d', '5d'] as const).map(delta => Aged.from({ createdAt: TimeUtil.fromNow(delta) }) )); const simple = await service.queryCount(Aged, { where: { createdAt: { $gt: new Date() } } }); assert(simple === 5); const simple2 = await service.queryCount(Aged, { where: { createdAt: { $gt: '-1d' } } }); assert(simple2 === 6); const simple3 = await service.queryCount(Aged, { where: { createdAt: { $gt: '-.9d', $lt: '2.9d' } } }); assert(simple3 === 3); const simple4 = await service.queryCount(Aged, { where: { createdAt: { $gt: TimeUtil.fromNow('-0.1d'), $lt: TimeUtil.fromNow('2.9d') } } }); assert(simple4 === 3); } @Test() async verifyArrayEmptyVsNot() { const service = await this.service; await service.create(WithNestedLists, { tags: ['a', 'b'] }); await service.create(WithNestedLists, { names: ['c', 'd'], }); await service.create(WithNestedLists, { names: ['c', 'd'], tags: ['e', 'f'] }); await service.create(WithNestedLists, { names: ['g', 'h'], tags: [] }); await service.create(WithNestedLists, { names: [], tags: [] }); let total = await service.queryCount(WithNestedLists, { where: { names: { $exists: true } } }); assert(total === 3); total = await service.queryCount(WithNestedLists, { where: { names: { $exists: false } } }); assert(total === 2); total = await service.queryCount(WithNestedLists, { where: { tags: { $exists: false } } }); assert(total === 3); total = await service.queryCount(WithNestedLists, { where: { tags: { $exists: true } } }); assert(total === 2); total = await service.queryCount(WithNestedLists, { where: { tags: { $exists: false }, names: { $exists: false } } }); assert(total === 1); total = await service.queryCount(WithNestedLists, { where: { tags: { $exists: true }, names: { $exists: true } } }); assert(total === 1); } @Test() async verifyNestedArrayEmptyVsNot() { const service = await this.service; await service.create(WithNestedNestedLists, { tags: ['a', 'b'] }); await service.create(WithNestedNestedLists, { sub: { names: ['c', 'd'] }, }); await service.create(WithNestedNestedLists, { sub: { names: ['c', 'd'] }, tags: ['e', 'f'] }); await service.create(WithNestedNestedLists, { sub: { names: ['g', 'h'] }, tags: [] }); await service.create(WithNestedNestedLists, { sub: {}, tags: [] }); let total = await service.queryCount(WithNestedNestedLists, { where: { sub: { names: { $exists: true } } } }); assert(total === 3); total = await service.queryCount(WithNestedNestedLists, { where: { sub: { names: { $exists: false } } } }); assert(total === 2); total = await service.queryCount(WithNestedNestedLists, { where: { tags: { $exists: false } } }); assert(total === 3); total = await service.queryCount(WithNestedNestedLists, { where: { tags: { $exists: true } } }); assert(total === 2); total = await service.queryCount(WithNestedNestedLists, { where: { tags: { $exists: false }, sub: { names: { $exists: false } } } }); assert(total === 1); total = await service.queryCount(WithNestedNestedLists, { where: { tags: { $exists: true }, sub: { names: { $exists: true } } } }); assert(total === 1); } }