From 4cc3324b46cd3774ad7b0daec695f148c03af8db Mon Sep 17 00:00:00 2001 From: Pablo Baleztena Date: Wed, 30 Oct 2024 22:42:13 -0300 Subject: [PATCH] [fabric/sqlite-store] Add QueryBuilder tests and implement assertNone method --- .../sqlite-store/state/query-builder.test.ts | 101 ++++++++++++++++++ .../sqlite-store/state/query-builder.ts | 38 +++++-- 2 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 packages/fabric/sqlite-store/state/query-builder.test.ts diff --git a/packages/fabric/sqlite-store/state/query-builder.test.ts b/packages/fabric/sqlite-store/state/query-builder.test.ts new file mode 100644 index 0000000..26beafc --- /dev/null +++ b/packages/fabric/sqlite-store/state/query-builder.test.ts @@ -0,0 +1,101 @@ +import { + AlreadyExistsError, + Field, + isLike, + Model, + NotFoundError, +} from "@fabric/domain"; +import { UUIDGeneratorMock } from "@fabric/domain/mocks"; +import { afterEach, beforeEach, describe, expect, test } from "@fabric/testing"; +import { SQLiteStateStore } from "./state-store.ts"; + +describe("QueryBuilder", () => { + const models = [ + Model.entityFrom("test", { + name: Field.string(), + }), + ]; + + let stateStore = new SQLiteStateStore(":memory:", models); + + beforeEach(async () => { + stateStore = new SQLiteStateStore(":memory:", models); + await stateStore.migrate().unwrapOrThrow(); + await stateStore.insertInto("test", { + id: UUIDGeneratorMock.generate(), + name: "test1", + }).unwrapOrThrow(); + await stateStore.insertInto("test", { + id: UUIDGeneratorMock.generate(), + name: "test2", + }).unwrapOrThrow(); + }); + + afterEach(async () => { + await stateStore.close().unwrapOrThrow(); + }); + + test("select() after a where() should return valid results", async () => { + const result = await stateStore.from("test").where({ + name: isLike("test%"), + }) + .select().unwrapOrThrow(); + expect(result).toEqual([{ + id: expect.any(String), + name: "test1", + }, { + id: expect.any(String), + name: "test2", + }]); + }); + + test("selectOneOrFail() should return a single result", async () => { + const result = await stateStore.from("test").where({ name: "test1" }) + .selectOneOrFail().unwrapOrThrow(); + expect(result).toEqual({ + id: expect.any(String), + name: "test1", + }); + }); + + test("selectOneOrFail() should fail if no results are found", async () => { + const error = await stateStore.from("test").where({ name: "not-found" }) + .selectOneOrFail().unwrapErrorOrThrow(); + + expect(error).toBeInstanceOf(NotFoundError); + }); + + test("selectOne() should return a single result", async () => { + const result = await stateStore.from("test") + .selectOne().unwrapOrThrow(); + + expect(result).toEqual({ + id: expect.any(String), + name: "test1", + }); + }); + + test("selectOne() should return undefined if no results are found", async () => { + const result = await stateStore.from("test").where({ + name: "not-found", + }) + .selectOne().unwrapOrThrow(); + + expect(result).toBeUndefined(); + }); + + test("assertNone() should succeed if no results are found", async () => { + const result = await stateStore.from("test").where({ + name: "not-found", + }).assertNone().unwrapOrThrow(); + + expect(result).toBeUndefined(); + }); + + test("assertNone() should fail if results are found", async () => { + const error = await stateStore.from("test").where({ name: "test1" }) + .assertNone().unwrapErrorOrThrow(); + + expect(error).toBeInstanceOf(AlreadyExistsError); + }); +}); diff --git a/packages/fabric/sqlite-store/state/query-builder.ts b/packages/fabric/sqlite-store/state/query-builder.ts index 35bc3eb..bfe372b 100644 --- a/packages/fabric/sqlite-store/state/query-builder.ts +++ b/packages/fabric/sqlite-store/state/query-builder.ts @@ -1,6 +1,7 @@ // deno-lint-ignore-file no-explicit-any import { AsyncResult, Keyof, Optional } from "@fabric/core"; import { + AlreadyExistsError, FilterOptions, Model, type ModelSchema, @@ -112,18 +113,43 @@ export class QueryBuilder implements StoreQuery { limit: 1, }, ); - const result = await this.db.onePrepared( + return await this.db.onePrepared( stmt, params, transformRow(this.schema[this.query.from]!), ); - if (!result) { - throw new NotFoundError(); - } - return result; }, (err) => new StoreQueryError(err.message), - ); + ).flatMap((result) => { + if (!result) { + return AsyncResult.failWith(new NotFoundError()); + } + return AsyncResult.ok(result); + }); + } + + assertNone(): AsyncResult { + return AsyncResult.tryFrom( + async () => { + const [stmt, params] = getSelectStatement( + this.schema[this.query.from]!, + { + ...this.query, + limit: 1, + }, + ); + return await this.db.onePrepared( + stmt, + params, + ); + }, + (err) => new StoreQueryError(err.message), + ).flatMap((result) => { + if (result) { + return AsyncResult.failWith(new AlreadyExistsError()); + } + return AsyncResult.ok(); + }); } }