node-cosmos
Version:
A light weight azure cosmosdb client aiming at ease of use for creating REST API. Supports json filter, sort and offset/limit
392 lines (348 loc) • 13.3 kB
text/typescript
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<void>) | 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();
});
});