diff --git a/README.md b/README.md index f999e92..f17c62e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ![Unit Tests](https://github.com/monolithst/functional-models-orm-memory/actions/workflows/ut.yml/badge.svg?branch=master) [![Coverage Status](https://coveralls.io/repos/github/monolithst/functional-models-orm-memory/badge.svg?branch=master)](https://coveralls.io/github/monolithst/functional-models-orm-memory?branch=master) + An ORM datastore adapter that is in-memory. Useful for testing and some caching applications. diff --git a/features/api.feature b/features/api.feature new file mode 100644 index 0000000..2f1f172 --- /dev/null +++ b/features/api.feature @@ -0,0 +1,22 @@ +Feature: API Functions + + Scenario: A simple "Cruds" on a model + Given a datastore using seed data SeedData1 is created + When count is called with Test1Models and Undefined + Then the result is 4 + When retrieve is called with Test1Models and Test1ModelDataId1 + Then the result is undefined + When save is called with Test1Models and data Test1ModelData + Then the result matches Test1ModelData + When retrieve is called with Test1Models and Test1ModelDataId1 + Then the result matches Test1ModelData + When count is called with Test1Models and Test1ModelDataId1 + Then the result is 5 + When search is called with Test1Models and SearchTest1Models1 + Then the result matches SearchTest1Models1Result + When delete is called with Test1Models and Test1ModelDataId1 + Then the result is undefined + When retrieve is called with Test1Models and Test1ModelDataId1 + Then the result is undefined + When count is called with Test1Models and Test1ModelDataId1 + Then the result is 4 diff --git a/features/step_definitions/steps.ts b/features/step_definitions/steps.ts new file mode 100644 index 0000000..2d43b98 --- /dev/null +++ b/features/step_definitions/steps.ts @@ -0,0 +1,96 @@ +import { assert } from 'chai' +import { Given, When, Then } from '@cucumber/cucumber' +import { datastoreAdapter } from '../../src' +import { createOrm, Orm, PrimaryKeyUuidProperty, queryBuilder, TextProperty } from 'functional-models' + +const SeedData = { + SeedData1: () => { + return { + 'functional-models-orm-memory-test-1-models': { + '29a766b5-e77b-4099-a7f2-61cda0a29cc3': { + id: '29a766b5-e77b-4099-a7f2-61cda0a29cc3', + name: 'my-name-1', + }, + '032c282c-b367-4d15-b19a-01c855b38f44': { + id: '032c282c-b367-4d15-b19a-01c855b38f44', + name: 'my-name-2', + }, + '49dd6b5b-cb33-4331-8224-98cc4fd4595a': { + id: '49dd6b5b-cb33-4331-8224-98cc4fd4595a', + name: 'my-name-3', + }, + 'd84734d3-2ec4-4da4-8cac-e2953394b7f4': { + id: 'd84734d3-2ec4-4da4-8cac-e2953394b7f4', + name: 'my-name-4', + }, + }, + } + } +} + +const Data = { + Undefined: () => undefined, + Test1ModelDataId1: () => 'cbac64c0-36c0-42c8-b736-57c5b7ba1c5a', + Test1ModelData: () => ({ + id: 'cbac64c0-36c0-42c8-b736-57c5b7ba1c5a', + name: 'another-name' + }), + SearchTest1Models1: () => queryBuilder() + .property('name', 'another-name') + .compile(), + SearchTest1Models1Result: () => ({ + instances: [{ + id: 'cbac64c0-36c0-42c8-b736-57c5b7ba1c5a', + name: 'another-name' + }], + page: undefined, + }) +} + +const Models = { + Test1Models: (orm: Orm) => { + return orm.Model({ + pluralName: 'Test1Models', + namespace: 'functional-models-orm-memory', + properties: { + id: PrimaryKeyUuidProperty(), + name: TextProperty(), + } + }) + } +} + +Given('a datastore using seed data {word} is created', function(key: string) { + const seedData = SeedData[key]() + this.datastoreAdapter = datastoreAdapter.create({seedData}) + this.orm = createOrm({ datastoreAdapter: this.datastoreAdapter }) +}) + +When('{word} is called with {word} and {word}', async function(funcKey: string, modelKey: string, dataKey: string) { + const model = Models[modelKey](this.orm) + const data = Data[dataKey]() + this.result = await this.datastoreAdapter[funcKey](model, data) +}) + +When('save is called with {word} and data {word}', async function(modelKey: string, dataKey: string) { + const model = Models[modelKey](this.orm) + const data = Data[dataKey]() + this.result = await this.datastoreAdapter.save(model.create(data)) +}) + +Then('the result is {int}', function(value: any) { + const actual = this.result + const expected = value + assert.deepEqual(actual, expected) +}) + +Then('the result is undefined', function() { + const actual = this.result + assert.isUndefined(actual) +}) + +Then('the result matches {word}', function(dataKey: string) { + const expected = Data[dataKey]() + const actual = this.result + assert.deepEqual(actual, expected) +}) diff --git a/package.json b/package.json index 6834754..5eb9f48 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "typescript": "^5.7.2" }, "dependencies": { + "date-fns": "^3.6.0", "functional-models": "^3.0.12", "lodash": "^4.17.21" } diff --git a/src/datastoreAdapter.ts b/src/datastoreAdapter.ts index 009f19d..0076217 100644 --- a/src/datastoreAdapter.ts +++ b/src/datastoreAdapter.ts @@ -1,5 +1,91 @@ -const create = () => { - return {} +import { + DataDescription, + DatastoreAdapter, + DatastoreSearchResult, + Maybe, + ModelInstance, + ModelType, + OrmModel, + OrmSearch, + PrimaryKeyType, + ToObjectResult, +} from 'functional-models' +import clone from 'lodash/clone' +import merge from 'lodash/merge' +import { filterResults } from './lib' + +type WithRequired = T & { [P in K]-?: T[P] } + +type Props = { + seedData?: Record> +} + +const create = ({ seedData }: Props = {}): WithRequired< + DatastoreAdapter, + 'count' +> => { + const database: Record> = clone( + seedData + ) || {} + + const _getRecords = ( + model: ModelType + ) => { + const name = model.getName() + if (!(name in database)) { + // eslint-disable-next-line functional/immutable-data + database[name] = {} + } + return database[name] as unknown as Record> + } + + return { + delete: ( + model: OrmModel, + id: PrimaryKeyType + ): Promise => { + const records = _getRecords(model) + // eslint-disable-next-line functional/immutable-data + delete records[id] + return Promise.resolve(undefined) + }, + retrieve: ( + model: OrmModel, + primaryKey: PrimaryKeyType + ): Promise>> => { + return Promise.resolve().then(() => { + const records = _getRecords(model) + return records[primaryKey] as unknown as + | ToObjectResult + | undefined + }) + }, + save: async ( + instance: ModelInstance + ): Promise> => { + return Promise.resolve().then(async () => { + const model = instance.getModel() + const data = await instance.toObj() + const records = _getRecords(model) + merge(records, { [await instance.getPrimaryKey()]: data }) + return data + }) + }, + search: ( + model: OrmModel, + query: OrmSearch + ): Promise> => { + const records = _getRecords(model) + const instances = filterResults(query, Object.values(records)) + return Promise.resolve({ instances: instances, page: undefined }) + }, + count: (model: OrmModel) => { + return Promise.resolve().then(() => { + const records = _getRecords(model) + return Object.keys(records).length + }) + }, + } } export { create } diff --git a/src/index.ts b/src/index.ts index 44903c9..82186ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ export * as datastoreAdapter from './datastoreAdapter' +export { filterResults } from './lib' diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 0000000..00bd699 --- /dev/null +++ b/src/lib.ts @@ -0,0 +1,192 @@ +import { isAfter } from 'date-fns/isAfter' +import { isBefore } from 'date-fns/isBefore' +import { + DataDescription, + DatastoreValueType, + DatesAfterQuery, + DatesBeforeQuery, + EqualitySymbol, + isALinkToken, + isPropertyBasedQuery, + OrmSearch, + PropertyQuery, + Query, + QueryTokens, + threeitize, + validateOrmSearch, +} from 'functional-models' +import { isEqual } from 'lodash' + +const _emptyValueWrapper = + (func: (property: PropertyQuery) => (obj: object) => boolean) => + (property: PropertyQuery) => { + const isEmptyCheck = property.value === undefined || property.value === null + const subfunc = func(property) + return (obj: object) => { + // @ts-ignore + const value = obj[property.key] + const valueIsEmpty = value === undefined || value === null + // Are we looking for empty and its empty? + if (isEmptyCheck && valueIsEmpty) { + return true + } + // Are we checking for a value but its empty? + if (!isEmptyCheck && valueIsEmpty) { + return false + } + // Both have values, time to compare. + return subfunc(obj) + } + } + +const _stringCompare = _emptyValueWrapper((property: PropertyQuery) => { + const rePrefix = property.options.startsWith ? '^' : '' + const reSuffix = property.options.endsWith ? '$' : '' + const flags = property.options.caseSensitive ? '' : 'i' + const re = new RegExp(`${rePrefix}${property.value}${reSuffix}`, flags) + return (obj: object) => { + // @ts-ignore + const value = obj[property.key] + return re.test(value) + } +}) + +const _checks = { + [EqualitySymbol.eq]: (searchValue: number, dataValue: number) => + searchValue === dataValue, + [EqualitySymbol.gt]: (searchValue: number, dataValue: number) => + searchValue < dataValue, + [EqualitySymbol.gte]: (searchValue: number, dataValue: number) => + searchValue <= dataValue, + [EqualitySymbol.lt]: (searchValue: number, dataValue: number) => + searchValue > dataValue, + [EqualitySymbol.lte]: (searchValue: number, dataValue: number) => + searchValue >= dataValue, +} + +const _numberCompare = _emptyValueWrapper((property: PropertyQuery) => { + return (obj: object) => { + // @ts-ignore + const value = obj[property.key] + return _checks[property.equalitySymbol](property.value, value) + } +}) + +const _booleanCompare = _emptyValueWrapper((property: PropertyQuery) => { + return (obj: object) => { + // @ts-ignore + const value = obj[property.key] + return property.value === value + } +}) + +const _objectCompare = _emptyValueWrapper((property: PropertyQuery) => { + const asObj = + typeof property.value === 'object' + ? property.value + : JSON.parse(property.value) + return (obj: object) => { + // @ts-ignore + const value = obj[property.key] + return isEqual(asObj, value) + } +}) + +const _typeToCompare: Record< + DatastoreValueType, + (property: PropertyQuery) => (obj: object) => boolean +> = { + [DatastoreValueType.number]: _numberCompare, + [DatastoreValueType.string]: _stringCompare, + [DatastoreValueType.date]: _stringCompare, + [DatastoreValueType.boolean]: _booleanCompare, + [DatastoreValueType.object]: _objectCompare, +} + +const _datesBeforeCheck = (o: DatesBeforeQuery) => (obj: object) => { + // @ts-ignore + const value = obj[o.key] + const dateA = new Date(o.date) + const dateB = new Date(value) + const before = isBefore(dateB, dateA) + return o.options.equalToAndBefore ? before || isEqual(dateA, dateB) : before +} + +const _datesAfterCheck = (o: DatesAfterQuery) => (obj: object) => { + // @ts-ignore + const value = obj[o.key] + const dateA = new Date(o.date) + const dateB = new Date(value) + const after = isAfter(dateB, dateA) + return o.options.equalToAndAfter ? after || isEqual(dateA, dateB) : after +} + +const _compareProperty = (property: Query) => { + if (property.type === 'property') { + return _typeToCompare[property.valueType](property) + } + if (property.type === 'datesBefore') { + return _datesBeforeCheck(property) + } + /* istanbul ignore next */ + if (property.type === 'datesAfter') { + return _datesAfterCheck(property) + } + /* istanbul ignore next */ + throw new Error('Impossible property situation') +} + +const _andCheck = (a: any, b: any) => (obj: object) => { + const r1 = a(obj) + const r2 = b(obj) + if (!r1) { + return false + } + return r2 +} +const _orCheck = (a: any, b: any) => (obj: object) => { + const r1 = a(obj) + const r2 = b(obj) + return r1 || r2 +} +const _allCheck = (listOfChecks: any[]) => (obj: object) => { + return listOfChecks.every(x => { + return x(obj) + }) +} + +const _buildChecks = (o: QueryTokens): ((obj: object) => boolean) => { + if (isPropertyBasedQuery(o)) { + return _compareProperty(o) + } + /* istanbul ignore next */ + if (Array.isArray(o)) { + // Is this just queries? + if (o.every(x => !isALinkToken(x))) { + return _allCheck(o.map(_buildChecks)) + } + + const threes = threeitize(o) + const checks = threes.reduce((acc, [a, link, b]) => { + const check1 = _buildChecks(a) + const check2 = _buildChecks(b) + const checkFunc = link.toLowerCase() === 'and' ? _andCheck : _orCheck + const combinedCheck = checkFunc(check1, check2) + return [...acc, combinedCheck] + }, []) + return _allCheck(checks) + } + /* istanbul ignore next */ + throw new Error('Should never happen') +} + +const filterResults = ( + searchQuery: OrmSearch, + databaseEntries: T[] +) => { + validateOrmSearch(searchQuery) + const func = _buildChecks(searchQuery.query) + return databaseEntries.filter(func) +} + +export { filterResults } diff --git a/test/src/datastoreAdapter.test.ts b/test/src/datastoreAdapter.test.ts index 0687325..15ec9f5 100644 --- a/test/src/datastoreAdapter.test.ts +++ b/test/src/datastoreAdapter.test.ts @@ -1,11 +1,140 @@ import { assert } from 'chai' +import { + createOrm, + PrimaryKeyUuidProperty, + queryBuilder, + TextProperty, +} from 'functional-models' import { create } from '../../src/datastoreAdapter' +const getSeedData1 = () => ({ + 'functional-models-orm-memory-test-1-models': { + '29a766b5-e77b-4099-a7f2-61cda0a29cc3': { + id: '29a766b5-e77b-4099-a7f2-61cda0a29cc3', + name: 'my-name-1', + }, + '032c282c-b367-4d15-b19a-01c855b38f44': { + id: '032c282c-b367-4d15-b19a-01c855b38f44', + name: 'my-name-2', + }, + '49dd6b5b-cb33-4331-8224-98cc4fd4595a': { + id: '49dd6b5b-cb33-4331-8224-98cc4fd4595a', + name: 'my-name-3', + }, + }, +}) + +type Test1Models = Readonly<{ + id: string + name: string +}> + +const setup = (seedData: any = undefined) => { + const datastoreAdapter = create({ seedData }) + const orm = createOrm({ datastoreAdapter }) + const models = { + Test1Models: orm.Model({ + pluralName: 'Test1Models', + namespace: 'functional-models-orm-memory', + properties: { + id: PrimaryKeyUuidProperty(), + name: TextProperty(), + }, + }), + } + + return { + datastoreAdapter, + orm, + models, + } +} + describe('/src/datastoreAdapter.ts', () => { describe('#create()', () => { it('should be able to create without any arguments', () => { const instance = create() assert.isOk(instance) }) + it('should create all 4 main functions + count', () => { + const instance = create() + const actual = Object.keys(instance) + const expected = ['save', 'delete', 'retrieve', 'search', 'count'] + assert.includeMembers(actual, expected) + }) + describe('#retrieve()', () => { + it('should return an object from the seedData when the primary key is provided', async () => { + const { datastoreAdapter, models } = setup(getSeedData1()) + const actual = await datastoreAdapter.retrieve( + models.Test1Models, + '032c282c-b367-4d15-b19a-01c855b38f44' + ) + const expected = { + id: '032c282c-b367-4d15-b19a-01c855b38f44', + name: 'my-name-2', + } + assert.deepEqual(actual, expected) + }) + it('should return undefined from the seedData an unknown primary key is provided', async () => { + const { datastoreAdapter, models } = setup(getSeedData1()) + const actual = await datastoreAdapter.retrieve( + models.Test1Models, + 'b9143f12-a2b1-45d1-83c2-9ecc9ac8d142' + ) + assert.isUndefined(actual) + }) + }) + describe('#delete()', () => { + it('should return undefined when using seedData when an actual primary key', async () => { + const { datastoreAdapter, models } = setup(getSeedData1()) + const actual = await datastoreAdapter.delete( + models.Test1Models, + '032c282c-b367-4d15-b19a-01c855b38f44' + ) + assert.isUndefined(actual) + }) + }) + describe('#save()', () => { + it('should return an object with a new id', async () => { + const { datastoreAdapter, models } = setup() + const actual = await datastoreAdapter.save( + models.Test1Models.create<'id'>({ name: 'my-name' }) + ) + assert.isOk(actual.id) + }) + }) + describe('#search()', () => { + it('should return one object name the name is provided in the search when using SeedData1', async () => { + const { datastoreAdapter, models } = setup(getSeedData1()) + const actual = await datastoreAdapter.search( + models.Test1Models, + queryBuilder().property('name', 'my-name-3').compile() + ) + const expected = { + page: undefined, + instances: [ + { + id: '49dd6b5b-cb33-4331-8224-98cc4fd4595a', + name: 'my-name-3', + }, + ], + } + assert.deepEqual(actual, expected) + }) + }) + describe('#count()', () => { + it('should return 3 when using SeedData1', async () => { + const { datastoreAdapter, models } = setup(getSeedData1()) + const actual = await datastoreAdapter.count(models.Test1Models) + const expected = 3 + assert.deepEqual(actual, expected) + }) + it('should return 0 when NOT using SeedData1', async () => { + const { datastoreAdapter, models } = setup() + const actual = await datastoreAdapter.count(models.Test1Models) + const expected = 0 + assert.deepEqual(actual, expected) + }) + }) }) }) diff --git a/test/src/lib.test.ts b/test/src/lib.test.ts new file mode 100644 index 0000000..3d0c438 --- /dev/null +++ b/test/src/lib.test.ts @@ -0,0 +1,321 @@ +import { assert } from 'chai' +import { + DatastoreValueType, + EqualitySymbol, + queryBuilder, +} from 'functional-models' +import { filterResults } from '../../src' + +const TestData1 = [ + { + id: 'id-1', + name: 'name-1', + aNumber: 7, + aBool: false, + }, + { + id: 'id-2', + name: 'name-2', + aNumber: 2, + aBool: false, + }, + { + id: 'id-3', + name: 'name-3', + aNumber: 8, + aBool: undefined, + }, + { + id: 'id-4', + name: null, + aNumber: undefined, + aBool: true, + }, + { + id: 'id-5', + name: undefined, + aNumber: 5, + aBool: undefined, + }, +] + +const DatedTestData1 = [ + { + id: 1, + datetime: new Date(Date.UTC(2025, 0, 1)), + }, + { + id: 2, + datetime: new Date(Date.UTC(2024, 11, 31)), + }, + { + id: 3, + datetime: new Date(Date.UTC(2025, 0, 2)), + }, + { + id: 4, + datetime: new Date('2025-01-01T00:00:00.001Z'), + }, + { + id: 5, + datetime: new Date('2024-12-31T23:59:59.999Z'), + }, + { + id: 6, + datetime: new Date(Date.UTC(2020, 0, 1)), + }, + { + id: 7, + datetime: new Date(Date.UTC(2027, 0, 1)), + }, + { + id: 8, + datetime: new Date(Date.UTC(2023, 0, 1)), + }, +] + +const TestData2 = [ + { + name: '1', + anObject: undefined, + }, + { + name: '2', + anObject: { nested: 'value', id: 1 }, + }, + { + name: '3', + anObject: { id: 3, nested: 'diff-value' }, + }, +] + +describe('/src/lib.ts', () => { + describe('#filterResults()', () => { + it('should return 1 result with TestData2 when searching an object as a stringifyied json', () => { + const actual = filterResults( + queryBuilder() + .property('anObject', JSON.stringify({ nested: 'value', id: 1 }), { + type: DatastoreValueType.object, + }) + .compile(), + TestData2 + ).length + const expected = 1 + assert.deepEqual(actual, expected) + }) + it('should return 1 result with TestData2 when searching an object with properties in the same order', () => { + const actual = filterResults( + queryBuilder() + .property( + 'anObject', + { nested: 'value', id: 1 }, + { type: DatastoreValueType.object } + ) + .compile(), + TestData2 + ).length + const expected = 1 + assert.deepEqual(actual, expected) + }) + it('should return 1 result with TestData2 when searching an object with properties in the reverse order', () => { + const actual = filterResults( + queryBuilder() + .property( + 'anObject', + { id: 1, nested: 'value' }, + { type: DatastoreValueType.object } + ) + .compile(), + TestData2 + ).length + const expected = 1 + assert.deepEqual(actual, expected) + }) + it('should return 3 results with DatedTestData1 when searching a span of time, AND including the exact time', () => { + const actual = filterResults( + queryBuilder() + .datesAfter('datetime', '2024-12-31T23:59:59.999Z', { + equalToAndAfter: true, + }) + .and() + .datesBefore('datetime', '2025-01-01T00:00:00.001Z', { + equalToAndBefore: true, + }) + .compile(), + DatedTestData1 + ).length + const expected = 3 + assert.deepEqual(actual, expected) + }) + it('should return 1 results with DatedTestData1 when searching a span of time, NOT including the exact time', () => { + const actual = filterResults( + queryBuilder() + .datesAfter('datetime', '2024-12-31T23:59:59.999Z', { + equalToAndAfter: false, + }) + .and() + .datesBefore('datetime', '2025-01-01T00:00:00.001Z', { + equalToAndBefore: false, + }) + .compile(), + DatedTestData1 + ).length + const expected = 1 + assert.deepEqual(actual, expected) + }) + it('should return 1 results with TestData1 when searching a property with endsWith', () => { + const actual = filterResults( + queryBuilder().property('name', '-1', { endsWith: true }).compile(), + TestData1 + ).length + const expected = 1 + assert.deepEqual(actual, expected) + }) + it('should return 2 results with TestData1 when searching a complex property query', () => { + const actual = filterResults( + queryBuilder() + .property('name', '-1', { endsWith: true }) + .or() + .complex(b => + b + .property('name', 'name', { startsWith: true }) + .and() + .complex(c => + c + .property('aNumber', 8, { type: DatastoreValueType.number }) + .or() + .property('aNumber', 7, { + type: DatastoreValueType.number, + }) + ) + ) + .compile(), + TestData1 + ).length + const expected = 2 + assert.deepEqual(actual, expected) + }) + it('should return 3 results with TestData1 when searching a property with startsWith', () => { + const actual = filterResults( + queryBuilder().property('name', 'name', { startsWith: true }).compile(), + TestData1 + ).length + const expected = 3 + assert.deepEqual(actual, expected) + }) + it('should return 2 results with TestData1 when searching two OR properties', () => { + const actual = filterResults( + queryBuilder() + .property('name', 'name-1') + .or() + .property('name', 'name-2') + .compile(), + TestData1 + ).length + const expected = 2 + assert.deepEqual(actual, expected) + }) + it('should return 1 result with TestData1 when searching a simple property', () => { + const actual = filterResults( + queryBuilder().property('name', 'name-1').compile(), + TestData1 + ).length + const expected = 1 + assert.deepEqual(actual, expected) + }) + it('should return 0 results with TestData1 when searching a simple property case sensitive=true', () => { + const actual = filterResults( + queryBuilder() + .property('name', 'Name-1', { caseSensitive: true }) + .compile(), + TestData1 + ).length + const expected = 0 + assert.deepEqual(actual, expected) + }) + it('should return 2 results with TestData1 when searching for empty values', () => { + const actual = filterResults( + queryBuilder().property('name', undefined).compile(), + TestData1 + ).length + const expected = 2 + assert.deepEqual(actual, expected) + }) + it('should return 2 results with TestData1 when searching <= 5', () => { + const actual = filterResults( + queryBuilder() + .property('aNumber', 5, { + type: DatastoreValueType.number, + equalitySymbol: EqualitySymbol.lte, + }) + .compile(), + TestData1 + ).length + const expected = 2 + assert.deepEqual(actual, expected) + }) + it('should return 4 results with TestData1 when searching > 1', () => { + const actual = filterResults( + queryBuilder() + .property('aNumber', 1, { + type: DatastoreValueType.number, + equalitySymbol: EqualitySymbol.gt, + }) + .compile(), + TestData1 + ).length + const expected = 4 + assert.deepEqual(actual, expected) + }) + it('should return 1 result with TestData1 when searching >= 8', () => { + const actual = filterResults( + queryBuilder() + .property('aNumber', 8, { + type: DatastoreValueType.number, + equalitySymbol: EqualitySymbol.gte, + }) + .compile(), + TestData1 + ).length + const expected = 1 + assert.deepEqual(actual, expected) + }) + it('should return 1 result with TestData1 when searching < 8', () => { + const actual = filterResults( + queryBuilder() + .property('aNumber', 8, { + type: DatastoreValueType.number, + equalitySymbol: EqualitySymbol.lt, + }) + .compile(), + TestData1 + ).length + const expected = 3 + assert.deepEqual(actual, expected) + }) + it('should return 1 result with TestData1 when searching true', () => { + const actual = filterResults( + queryBuilder() + .property('aBool', true, { + type: DatastoreValueType.boolean, + }) + .compile(), + TestData1 + ).length + const expected = 1 + assert.deepEqual(actual, expected) + }) + it('should return 2 result with TestData1 when searching false', () => { + const actual = filterResults( + queryBuilder() + .property('aBool', false, { + type: DatastoreValueType.boolean, + }) + .compile(), + TestData1 + ).length + const expected = 2 + assert.deepEqual(actual, expected) + }) + }) +})