diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 536992d655..fd4d8cf7c8 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -68,7 +68,7 @@ jobs: run: yarn build:oss - name: Build account portal run: yarn build:account-portal - if: inputs.run_as_oss != true + if: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }} # Check the types of the projects built via esbuild - name: Check types run: | diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index 3fabbfbef9..fdf1ed7603 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -6,10 +6,12 @@ import { Datasource, EmptyFilterOption, FieldType, - Row, + RowSearchParams, SearchFilters, Table, + TableSchema, } from "@budibase/types" +import _ from "lodash" jest.unmock("mssql") @@ -25,8 +27,8 @@ describe.each([ const config = setup.getConfig() let envCleanup: (() => void) | undefined - let table: Table let datasource: Datasource | undefined + let table: Table beforeAll(async () => { if (isSqs) { @@ -47,231 +49,217 @@ describe.each([ } }) + async function createTable(schema: TableSchema) { + table = await config.api.table.save( + tableForDatasource(datasource, { schema }) + ) + } + + async function createRows(rows: Record[]) { + await Promise.all(rows.map(r => config.api.row.save(table._id!, r))) + } + + class SearchAssertion { + constructor(private readonly query: RowSearchParams) {} + + async toFind(expectedRows: any[]) { + const { rows: foundRows } = await config.api.row.search(table._id!, { + ...this.query, + tableId: table._id!, + }) + + // eslint-disable-next-line jest/no-standalone-expect + expect(foundRows).toHaveLength(expectedRows.length) + // eslint-disable-next-line jest/no-standalone-expect + expect(foundRows).toEqual( + expect.arrayContaining( + expectedRows.map((expectedRow: any) => + expect.objectContaining( + foundRows.find(foundRow => _.isMatch(foundRow, expectedRow)) + ) + ) + ) + ) + } + + async toFindNothing() { + await this.toFind([]) + } + } + + function expectSearch(query: Omit) { + return new SearchAssertion({ ...query, tableId: table._id! }) + } + + function expectQuery(query: SearchFilters) { + return expectSearch({ query }) + } + describe("strings", () => { beforeAll(async () => { - table = await config.api.table.save( - tableForDatasource(datasource, { - schema: { - name: { - name: "name", - type: FieldType.STRING, - }, - }, - }) - ) + await createTable({ + name: { name: "name", type: FieldType.STRING }, + }) + await createRows([{ name: "foo" }, { name: "bar" }]) }) - const rows = [{ name: "foo" }, { name: "bar" }] - let savedRows: Row[] + describe("misc", () => { + it("should return all if no query is passed", () => + expectSearch({} as RowSearchParams).toFind([ + { name: "foo" }, + { name: "bar" }, + ])) - beforeAll(async () => { - savedRows = await Promise.all( - rows.map(r => config.api.row.save(table._id!, r)) - ) + it("should return all if empty query is passed", () => + expectQuery({}).toFind([{ name: "foo" }, { name: "bar" }])) + + it("should return all if onEmptyFilter is RETURN_ALL", () => + expectQuery({ + onEmptyFilter: EmptyFilterOption.RETURN_ALL, + }).toFind([{ name: "foo" }, { name: "bar" }])) + + it("should return nothing if onEmptyFilter is RETURN_NONE", () => + expectQuery({ + onEmptyFilter: EmptyFilterOption.RETURN_NONE, + }).toFindNothing()) }) - interface StringSearchTest { - query: SearchFilters - expected: (typeof rows)[number][] - } + describe("equal", () => { + it("successfully finds a row", () => + expectQuery({ equal: { name: "foo" } }).toFind([{ name: "foo" }])) - const stringSearchTests: StringSearchTest[] = [ - // These three test cases are generic and don't really need - // to be repeated for all data types, so we just do them here. - { query: {}, expected: rows }, - { - query: { onEmptyFilter: EmptyFilterOption.RETURN_ALL }, - expected: rows, - }, - { - query: { onEmptyFilter: EmptyFilterOption.RETURN_NONE }, - expected: [], - }, - // The rest of these tests are specific to strings. - { query: { string: { name: "foo" } }, expected: [rows[0]] }, - { query: { string: { name: "none" } }, expected: [] }, - { query: { fuzzy: { name: "oo" } }, expected: [rows[0]] }, - { query: { equal: { name: "foo" } }, expected: [rows[0]] }, - { query: { notEqual: { name: "foo" } }, expected: [rows[1]] }, - { query: { oneOf: { name: ["foo"] } }, expected: [rows[0]] }, - ] + it("fails to find nonexistent row", () => + expectQuery({ equal: { name: "none" } }).toFindNothing()) + }) - it.each(stringSearchTests)( - `should be able to run query: $query`, - async ({ query, expected }) => { - const { rows: foundRows } = await config.api.row.search(table._id!, { - tableId: table._id!, - query, - }) - expect(foundRows).toHaveLength(expected.length) - expect(foundRows).toEqual( - expect.arrayContaining( - expected.map(r => - expect.objectContaining(savedRows.find(sr => sr.name === r.name)!) - ) - ) - ) - } - ) + describe("notEqual", () => { + it("successfully finds a row", () => + expectQuery({ notEqual: { name: "foo" } }).toFind([{ name: "bar" }])) + + it("fails to find nonexistent row", () => + expectQuery({ notEqual: { name: "bar" } }).toFind([{ name: "foo" }])) + }) + + describe("oneOf", () => { + it("successfully finds a row", () => + expectQuery({ oneOf: { name: ["foo"] } }).toFind([{ name: "foo" }])) + + it("fails to find nonexistent row", () => + expectQuery({ oneOf: { name: ["none"] } }).toFindNothing()) + }) + + describe("fuzzy", () => { + it("successfully finds a row", () => + expectQuery({ fuzzy: { name: "oo" } }).toFind([{ name: "foo" }])) + + it("fails to find nonexistent row", () => + expectQuery({ fuzzy: { name: "none" } }).toFindNothing()) + }) }) - describe("number", () => { + describe("numbers", () => { beforeAll(async () => { - table = await config.api.table.save( - tableForDatasource(datasource, { - schema: { - age: { - name: "age", - type: FieldType.NUMBER, - }, - }, - }) - ) + await createTable({ + age: { name: "age", type: FieldType.NUMBER }, + }) + await createRows([{ age: 1 }, { age: 10 }]) }) - const rows = [{ age: 1 }, { age: 10 }] - let savedRows: Row[] + describe("equal", () => { + it("successfully finds a row", () => + expectQuery({ equal: { age: 1 } }).toFind([{ age: 1 }])) - beforeAll(async () => { - savedRows = await Promise.all( - rows.map(r => config.api.row.save(table._id!, r)) - ) + it("fails to find nonexistent row", () => + expectQuery({ equal: { age: 2 } }).toFindNothing()) }) - interface NumberSearchTest { - query: SearchFilters - expected: (typeof rows)[number][] - } + describe("notEqual", () => { + it("successfully finds a row", () => + expectQuery({ notEqual: { age: 1 } }).toFind([{ age: 10 }])) - const numberSearchTests: NumberSearchTest[] = [ - { query: { equal: { age: 1 } }, expected: [rows[0]] }, - { query: { equal: { age: 2 } }, expected: [] }, - { query: { notEqual: { age: 1 } }, expected: [rows[1]] }, - { query: { oneOf: { age: [1] } }, expected: [rows[0]] }, - { query: { range: { age: { low: 1, high: 5 } } }, expected: [rows[0]] }, - { query: { range: { age: { low: 0, high: 1 } } }, expected: [rows[0]] }, - { query: { range: { age: { low: 3, high: 4 } } }, expected: [] }, - { query: { range: { age: { low: 0, high: 11 } } }, expected: rows }, - ] + it("fails to find nonexistent row", () => + expectQuery({ notEqual: { age: 10 } }).toFind([{ age: 1 }])) + }) - it.each(numberSearchTests)( - `should be able to run query: $query`, - async ({ query, expected }) => { - const { rows: foundRows } = await config.api.row.search(table._id!, { - tableId: table._id!, - query, - }) - expect(foundRows).toHaveLength(expected.length) - expect(foundRows).toEqual( - expect.arrayContaining( - expected.map(r => - expect.objectContaining(savedRows.find(sr => sr.age === r.age)!) - ) - ) - ) - } - ) + describe("oneOf", () => { + it("successfully finds a row", () => + expectQuery({ oneOf: { age: [1] } }).toFind([{ age: 1 }])) + + it("fails to find nonexistent row", () => + expectQuery({ oneOf: { age: [2] } }).toFindNothing()) + }) + + describe("range", () => { + it("successfully finds a row", () => + expectQuery({ + range: { age: { low: 1, high: 5 } }, + }).toFind([{ age: 1 }])) + + it("successfully finds multiple rows", () => + expectQuery({ + range: { age: { low: 1, high: 10 } }, + }).toFind([{ age: 1 }, { age: 10 }])) + + it("successfully finds a row with a high bound", () => + expectQuery({ + range: { age: { low: 5, high: 10 } }, + }).toFind([{ age: 10 }])) + }) }) describe("dates", () => { - beforeEach(async () => { - table = await config.api.table.save( - tableForDatasource(datasource, { - schema: { - dob: { - name: "dob", - type: FieldType.DATETIME, - }, - }, - }) - ) + const JAN_1ST = "2020-01-01T00:00:00.000Z" + const JAN_2ND = "2020-01-02T00:00:00.000Z" + const JAN_5TH = "2020-01-05T00:00:00.000Z" + const JAN_10TH = "2020-01-10T00:00:00.000Z" + + beforeAll(async () => { + await createTable({ + dob: { name: "dob", type: FieldType.DATETIME }, + }) + + await createRows([{ dob: JAN_1ST }, { dob: JAN_10TH }]) }) - const rows = [ - { dob: new Date("2020-01-01").toISOString() }, - { dob: new Date("2020-01-10").toISOString() }, - ] - let savedRows: Row[] + describe("equal", () => { + it("successfully finds a row", () => + expectQuery({ equal: { dob: JAN_1ST } }).toFind([{ dob: JAN_1ST }])) - beforeEach(async () => { - savedRows = await Promise.all( - rows.map(r => config.api.row.save(table._id!, r)) - ) + it("fails to find nonexistent row", () => + expectQuery({ equal: { dob: JAN_2ND } }).toFindNothing()) }) - interface DateSearchTest { - query: SearchFilters - expected: (typeof rows)[number][] - } + describe("notEqual", () => { + it("successfully finds a row", () => + expectQuery({ notEqual: { dob: JAN_1ST } }).toFind([{ dob: JAN_10TH }])) - const dateSearchTests: DateSearchTest[] = [ - { - query: { equal: { dob: new Date("2020-01-01").toISOString() } }, - expected: [rows[0]], - }, - { - query: { equal: { dob: new Date("2020-01-02").toISOString() } }, - expected: [], - }, - { - query: { notEqual: { dob: new Date("2020-01-01").toISOString() } }, - expected: [rows[1]], - }, - { - query: { oneOf: { dob: [new Date("2020-01-01").toISOString()] } }, - expected: [rows[0]], - }, - { - query: { - range: { - dob: { - low: new Date("2020-01-01").toISOString(), - high: new Date("2020-01-05").toISOString(), - }, - }, - }, - expected: [rows[0]], - }, - { - query: { - range: { - dob: { - low: new Date("2020-01-01").toISOString(), - high: new Date("2020-01-10").toISOString(), - }, - }, - }, - expected: rows, - }, - { - query: { - range: { - dob: { - low: new Date("2020-01-05").toISOString(), - high: new Date("2020-01-10").toISOString(), - }, - }, - }, - expected: [rows[1]], - }, - ] + it("fails to find nonexistent row", () => + expectQuery({ notEqual: { dob: JAN_10TH } }).toFind([{ dob: JAN_1ST }])) + }) - it.each(dateSearchTests)( - `should be able to run query: $query`, - async ({ query, expected }) => { - const { rows: foundRows } = await config.api.row.search(table._id!, { - tableId: table._id!, - query, - }) - expect(foundRows).toHaveLength(expected.length) - expect(foundRows).toEqual( - expect.arrayContaining( - expected.map(r => - expect.objectContaining(savedRows.find(sr => sr.dob === r.dob)!) - ) - ) - ) - } - ) + describe("oneOf", () => { + it("successfully finds a row", () => + expectQuery({ oneOf: { dob: [JAN_1ST] } }).toFind([{ dob: JAN_1ST }])) + + it("fails to find nonexistent row", () => + expectQuery({ oneOf: { dob: [JAN_2ND] } }).toFindNothing()) + }) + + describe("range", () => { + it("successfully finds a row", () => + expectQuery({ + range: { dob: { low: JAN_1ST, high: JAN_5TH } }, + }).toFind([{ dob: JAN_1ST }])) + + it("successfully finds multiple rows", () => + expectQuery({ + range: { dob: { low: JAN_1ST, high: JAN_10TH } }, + }).toFind([{ dob: JAN_1ST }, { dob: JAN_10TH }])) + + it("successfully finds a row with a high bound", () => + expectQuery({ + range: { dob: { low: JAN_5TH, high: JAN_10TH } }, + }).toFind([{ dob: JAN_10TH }])) + }) }) })