import { CosmosDatabase, CosmosDocument } from "../../../src/cosmos/CosmosDatabase"; import { PostgresImpl } from "../../../src/cosmos/impl/postgresql/PostgresImpl"; import { createPostgresTestEnvironment } from "./utils/postgresTestUtils"; import dotenv from "dotenv"; import randomstring from "randomstring"; dotenv.config(); // load .env file to process.env let db: CosmosDatabase; let account: PostgresImpl; let cleanup: (() => Promise) | undefined; const SCHEMA_NAME = "UnitTest_Postgres_" + randomstring.generate(7); const USERS_TABLE = "Users"; const MEMBERS_TABLE = "Members"; describe("PostgresImpl Test", () => { beforeAll(async () => { const env = await createPostgresTestEnvironment([ { schema: SCHEMA_NAME, tables: [USERS_TABLE, MEMBERS_TABLE] }, ]); cleanup = env.cleanup; account = new PostgresImpl(env.connectionString, () => env.pool); db = await account.getDatabase("CosmosDB"); await db.createCollection(SCHEMA_NAME); }); afterAll(async () => { if (cleanup) { await cleanup(); } await account.close(); }); it("create and read items", async () => { const origin = { id: "user_create_id01" + randomstring.generate(7), firstName: "Anony", lastName: "Nobody", }; try { await db.delete(SCHEMA_NAME, origin.id, USERS_TABLE); const user1 = await db.create(SCHEMA_NAME, origin, USERS_TABLE); expect(user1.id).toEqual(origin.id); expect(user1.firstName).toEqual(origin.firstName); expect(user1._partition).toEqual(USERS_TABLE); const read1 = await db.read(SCHEMA_NAME, origin.id, USERS_TABLE); expect(read1.id).toEqual(user1.id); expect(read1.lastName).toEqual(origin.lastName); } finally { await db.delete(SCHEMA_NAME, origin.id, USERS_TABLE); } }); it("invalid id should not be created or upserted", async () => { const origin = { id: "user_create_id01_\t_tab", firstName: "Anony", lastName: "Nobody", }; await expect(db.create(SCHEMA_NAME, origin, USERS_TABLE)).rejects.toThrow( "id cannot contain", ); await expect(db.upsert(SCHEMA_NAME, origin, USERS_TABLE)).rejects.toThrow( "id cannot contain", ); }); it("update items", async () => { const origin = { id: "user_update_id01", firstName: "Anony", lastName: "Nobody", }; try { await db.create(SCHEMA_NAME, origin, USERS_TABLE); const user1 = { id: "user_update_id01", firstName: "Updated" }; const updated = await db.update(SCHEMA_NAME, user1, USERS_TABLE); expect(updated.id).toEqual(origin.id); expect(updated.firstName).toEqual(user1.firstName); expect(updated.lastName).toEqual(origin.lastName); } finally { await db.delete(SCHEMA_NAME, origin.id, USERS_TABLE); } }); it("upsert, find and count items", async () => { const origin = { id: "user_upsert_id01", firstName: "Anony", lastName: "Nobody", address: { country: "Japan", city: "Tokyo", }, tags: ["react", "java"], skills: [ { id: "S001", name: "fishing", }, { id: "S002", name: "hunting", }, ], }; const origin2 = { id: "user_upsert_id02", firstName: "Tom", lastName: "Luck", address: { country: "Japan", city: "Osaka", }, tags: ["react", "typescript"], skills: [ { id: "S001", name: "fishing", }, { id: "S003", name: "swimming", }, ], }; const origin3 = { id: "user_upsert_id03", firstName: "Judy", lastName: "Hawks", address: { country: "England", city: "London", }, tags: ["java", "go"], }; try { const upserted1 = await db.upsert(SCHEMA_NAME, origin, USERS_TABLE); await db.upsert(SCHEMA_NAME, origin2, USERS_TABLE); await db.upsert(SCHEMA_NAME, origin3, MEMBERS_TABLE); expect(upserted1.id).toEqual(origin.id); expect(upserted1.firstName).toEqual(origin.firstName); expect(upserted1.lastName).toEqual(origin.lastName); const read = await db.read(SCHEMA_NAME, origin.id, USERS_TABLE); expect(read.firstName).toEqual(origin.firstName); const partialUpdate = { id: origin.id, lastName: "partialUpdate" }; const updated2 = await db.update(SCHEMA_NAME, partialUpdate, USERS_TABLE); expect(updated2.id).toEqual(origin.id); expect(updated2.firstName).toEqual(origin.firstName); expect(updated2.lastName).toEqual(partialUpdate.lastName); { const items = await db.find( SCHEMA_NAME, { filter: { "id%": "user_upsert" }, sort: ["id", "ASC"] }, USERS_TABLE, ); expect(items.length).toEqual(2); expect(items[0]["firstName"]).toEqual(origin.firstName); expect(items[1]["lastName"]).toEqual(origin2.lastName); } { const items = await db.find( SCHEMA_NAME, { filter: { "lastName CONTAINS": "Upd", }, sort: ["firstName", "ASC"], offset: 0, limit: 100, }, USERS_TABLE, ); expect(items[0]["id"]).toEqual(origin.id); } { let items = await db.find( SCHEMA_NAME, { filter: { "address.city LIKE": "%saka", }, sort: ["id", "ASC"], }, USERS_TABLE, ); expect(items[0]["id"]).toEqual(origin2.id); items = await db.find( SCHEMA_NAME, { filter: { "address.city LIKE": "%k%", }, sort: ["id", "ASC"], }, USERS_TABLE, ); expect(items.length).toEqual(2); expect(items[0]["id"]).toEqual(origin.id); expect(items[1]["id"]).toEqual(origin2.id); } { const count = await db.count( SCHEMA_NAME, { filter: { "id >": "user_upsert_id01", lastName: [origin2.lastName], }, sort: ["firstName", "ASC"], offset: 0, limit: 100, }, USERS_TABLE, ); expect(count).toEqual(1); } { const count = await db.count( SCHEMA_NAME, { filter: {}, sort: ["firstName", "ASC"], offset: 0, limit: 1, }, USERS_TABLE, ); expect(count).toEqual(2); } { const items = await db.find( SCHEMA_NAME, { filter: { "tags ARRAY_CONTAINS_ANY": "react", }, sort: ["id", "ASC"], }, USERS_TABLE, ); expect(items.length).toEqual(2); expect(items[0]["id"]).toEqual(origin.id); expect(items[1]["id"]).toEqual(origin2.id); } { let items = await db.find( SCHEMA_NAME, { filter: { "skills ARRAY_CONTAINS_ALL name": ["swimming", "hunting"], }, sort: ["id", "ASC"], }, USERS_TABLE, ); expect(items.length).toEqual(0); items = await db.find( SCHEMA_NAME, { filter: { "skills ARRAY_CONTAINS_ALL name": ["swimming", "fishing"], }, sort: ["id", "ASC"], }, USERS_TABLE, ); expect(items.length).toEqual(1); expect(items[0]["id"]).toEqual(origin2.id); } } finally { await db.delete(SCHEMA_NAME, origin.id, USERS_TABLE); await db.delete(SCHEMA_NAME, origin2.id, USERS_TABLE); await db.delete(SCHEMA_NAME, origin3.id, MEMBERS_TABLE); } }); it("adds _expireAt when ttl is provided", async () => { const id = "user_ttl_id01" + randomstring.generate(5); const origin = { id, firstName: "TTL", lastName: "User", ttl: 30, }; let currentNow = 1_700_000_000_000; const nowSpy = jest.spyOn(Date, "now").mockImplementation(() => currentNow); try { await db.delete(SCHEMA_NAME, id, USERS_TABLE); const created = await db.create(SCHEMA_NAME, origin, USERS_TABLE); expect(typeof created._expireAt).toBe("number"); expect(created._expireAt).toEqual(Math.floor(currentNow / 1000) + (origin.ttl || 0)); currentNow = 1_700_000_100_000; const updated = await db.update( SCHEMA_NAME, { id, ttl: 120, lastName: "TTLUpdated" }, USERS_TABLE, ); expect(updated._expireAt).toEqual(Math.floor(currentNow / 1000) + 120); currentNow = 1_700_000_200_000; const upserted = await db.upsert( SCHEMA_NAME, { id, ttl: 300, firstName: "Upserted" }, USERS_TABLE, ); expect(upserted._expireAt).toEqual(Math.floor(currentNow / 1000) + 300); currentNow = 1_700_000_300_000; const ttlRemoved = await db.update( SCHEMA_NAME, { id, ttl: undefined, lastName: "TTLRemoved" }, USERS_TABLE, ); expect(ttlRemoved._expireAt).toBeUndefined(); expect(ttlRemoved.ttl).toBeUndefined(); currentNow = 1_700_000_400_000; const zeroTtl = await db.update( SCHEMA_NAME, { id, ttl: 0, lastName: "ZeroTTL" }, USERS_TABLE, ); expect(zeroTtl._expireAt).toEqual(Math.floor(currentNow / 1000)); currentNow = 1_700_000_500_000; const negativeTtl = await db.update( SCHEMA_NAME, { id, ttl: -60, lastName: "NegativeTTL" }, USERS_TABLE, ); expect(negativeTtl._expireAt).toEqual(Math.floor(currentNow / 1000) - 60); } finally { nowSpy.mockRestore(); await db.delete(SCHEMA_NAME, id, USERS_TABLE); } }); it("404 error will be thrown when reading not exist item", async () => { const origin = { id: "user_read_not_exist", firstName: "Anony", lastName: "Nobody", }; await db.delete(SCHEMA_NAME, origin.id, USERS_TABLE); try { await db.read(SCHEMA_NAME, origin.id, USERS_TABLE); } catch (err) { const message = typeof err === "object" && err !== null && "message" in err ? String((err as { message?: string }).message) : String(err); expect(message).toContain("item not found"); return; } throw new Error("read should have thrown"); }); it("default value should be returned if not exist", async () => { const user = await db.readOrDefault(SCHEMA_NAME, "NotExistId", USERS_TABLE, null); expect(user).toBeNull(); }); });