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
466 lines (402 loc) • 15.1 kB
text/typescript
import { CosmosDatabase, CosmosDocument } from "../../../src/cosmos/CosmosDatabase";
import { MongoImpl } from "../../../src/cosmos/impl/mongodb/MongoImpl";
import dotenv from "dotenv";
import randomstring from "randomstring";
dotenv.config(); // load .env file to process.env
let db: CosmosDatabase;
let account: MongoImpl;
export const LOCAL_CONNECTION_STRING = "mongodb://localhost:27017/?replicaSet=rs0";
const host = "UnitTestNodeMongo" + randomstring.generate(7);
/**
* Type guard for error
* @param e error
* @returns error instance
*/
function isError(e: unknown): e is Error {
return e instanceof Error;
}
describe("MongoImpl Test", () => {
beforeAll(async () => {
account = new MongoImpl(
process.env.MONGODB_CONNECTION_STRING || LOCAL_CONNECTION_STRING,
true,
true,
);
db = await account.getDatabase(host);
// error will not occur if the db already exist
await account._createDatabaseIfNotExist(host);
});
afterAll(async () => {
await account.deleteDatabase(host);
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(host, origin.id, "Users");
const user1 = await db.create(host, origin, "Users");
expect(user1.id).toEqual(origin.id);
expect(user1.firstName).toEqual(origin.firstName);
expect(user1._partition).toEqual("Users");
expect(user1._expireAt).toBeUndefined();
const read1 = await db.read(host, origin.id, "Users");
expect(read1.id).toEqual(user1.id);
expect(read1.lastName).toEqual(origin.lastName);
} finally {
await db.delete(host, origin.id, "Users");
}
});
it("invalid id should not be created or upserted", async () => {
const origin = {
id: "user_create_id01_\t_tab", // invalid id which contains tab
firstName: "Anony",
lastName: "Nobody",
};
try {
await db.create(host, origin, "Users");
fail("create should not succeed");
} catch (e) {
if (isError(e)) {
expect(e.message).toContain("id cannot contain");
}
}
try {
await db.upsert(host, origin, "Users");
fail("upsert should not succeed");
} catch (e) {
if (isError(e)) {
expect(e.message).toContain("id cannot contain");
}
}
});
it("update items", async () => {
const origin = {
id: "user_update_id01",
firstName: "Anony",
lastName: "Nobody",
};
try {
await db.create(host, origin, "Users");
const user1 = { id: "user_update_id01", firstName: "Updated" };
const updated = await db.update(host, user1, "Users");
expect(updated.id).toEqual(origin.id);
expect(updated.firstName).toEqual(user1.firstName);
expect(updated.lastName).toEqual(origin.lastName);
} finally {
await db.delete(host, origin.id, "Users");
}
});
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 {
// prepare data
const upserted1 = await db.upsert(host, origin, "Users");
await db.upsert(host, origin2, "Users");
// a different partition
await db.upsert(host, origin3, "Members");
//begin assertions
expect(upserted1.id).toEqual(origin.id);
expect(upserted1.firstName).toEqual(origin.firstName);
expect(upserted1.lastName).toEqual(origin.lastName);
const read = await db.read(host, origin.id, "Users");
expect(read.firstName).toEqual(origin.firstName);
//partial update
const partialUpdate = { id: origin.id, lastName: "partialUpdate" };
const updated2 = await db.update(host, partialUpdate, "Users");
expect(updated2.id).toEqual(origin.id);
expect(updated2.firstName).toEqual(origin.firstName);
expect(updated2.lastName).toEqual(partialUpdate.lastName);
{
//find should work, using special LIKE
const items = await db.find(
host,
{ filter: { "id%": "user_upsert" }, sort: ["id", "ASC"] },
"Users",
);
expect(items?.length).toEqual(2);
expect(items[0]["firstName"]).toEqual(origin.firstName);
//system fields are removed
expect(items[0]["_rid"]).toEqual(undefined);
expect(items[1]["lastName"]).toEqual(origin2.lastName);
}
{
//find using CONTAINS
const items = await db.find(
host,
{
filter: {
"lastName CONTAINS": "Upd",
},
sort: ["firstName", "ASC"],
offset: 0,
limit: 100,
},
"Users",
);
expect(items[0]["id"]).toEqual(origin.id);
//system fields are removed
expect(items[0]["_rid"]).toEqual(undefined);
}
{
//find using LIKE
let items = await db.find(
host,
{
filter: {
"address.city LIKE": "%saka",
},
sort: ["id", "ASC"],
},
"Users",
);
expect(items[0]["id"]).toEqual(origin2.id);
items = await db.find(
host,
{
filter: {
"address.city LIKE": "%k%",
},
sort: ["id", "ASC"],
},
"Users",
);
expect(items.length).toEqual(2);
expect(items[0]["id"]).toEqual(origin.id);
expect(items[1]["id"]).toEqual(origin2.id);
}
{
//count
const count = await db.count(
host,
{
filter: {
"id >": "user_upsert_id01", // id equals "user_upsert_id01"
lastName: [origin2.lastName],
},
sort: ["firstName", "ASC"],
offset: 0,
limit: 100,
},
"Users",
);
expect(count).toEqual(1);
}
{
//count should ignore offset and limit
const count = await db.count(
host,
{
filter: {},
sort: ["firstName", "ASC"],
offset: 0,
limit: 1,
},
"Users",
);
expect(count).toEqual(2);
}
{
// find / count using cross partition is not supported for mongodb
}
{
//ARRAY_CONTAINS_ANY
const items = await db.find(
host,
{
filter: {
"tags ARRAY_CONTAINS_ANY": "react",
},
sort: ["id", "ASC"],
},
"Users",
);
expect(items.length).toEqual(2);
expect(items[0]["id"]).toEqual(origin.id);
expect(items[1]["id"]).toEqual(origin2.id);
}
{
//ARRAY_CONTAINS_ALL
let items = await db.find(
host,
{
filter: {
"skills ARRAY_CONTAINS_ALL name": ["swimming", "hunting"],
},
sort: ["id", "ASC"],
},
"Users",
);
// nobody fit this condition
expect(items.length).toEqual(0);
items = await db.find(
host,
{
filter: {
"skills ARRAY_CONTAINS_ALL name": ["swimming", "fishing"],
},
sort: ["id", "ASC"],
},
"Users",
);
//origin2 fits
expect(items.length).toEqual(1);
expect(items[0]["id"]).toEqual(origin2.id);
}
} finally {
await db.delete(host, origin.id, "Users");
await db.delete(host, origin2.id, "Users");
await db.delete(host, origin3.id, "Members");
}
});
it("404 error will be thrown when reading not exist item", async () => {
const origin = {
id: "user_read_not_exist",
firstName: "Anony",
lastName: "Nobody",
};
try {
await db.read(host, origin.id, "Users");
} catch (err) {
const eStr = JSON.stringify(err, Object.getOwnPropertyNames(err));
expect(eStr.includes("404")).toEqual(true);
} finally {
await db.delete(host, origin.id, "Users");
}
});
it("404 error will be thrown when reading not exist item", async () => {
const origin = {
id: "user_read_not_exist",
firstName: "Anony",
lastName: "Nobody",
};
try {
await db.read(host, origin.id, "Users");
} catch (err) {
const eStr = JSON.stringify(err, Object.getOwnPropertyNames(err));
expect(eStr.includes("404")).toEqual(true);
} finally {
await db.delete(host, origin.id, "Users");
}
});
it("default value should be returned if not exist", async () => {
try {
const user = await db.readOrDefault(host, "NotExistId", "Users", null);
expect(user).toBeNull();
} catch (err) {
fail("should not throw exception");
}
});
it("ttl and expireAt should be set correctly", async () => {
const thirtyDaysInSeconds = 30 * 24 * 60 * 60;
const origin = {
id: "ttl_and_expireAt_" + randomstring.generate(7),
firstName: "Tom",
lastName: "Banks",
ttl: thirtyDaysInSeconds,
};
try {
await db.delete(host, origin.id, "Users");
// test create
const user1 = await db.create(host, origin, "Users");
expect(user1.id).toEqual(origin.id);
expect(user1.firstName).toEqual(origin.firstName);
expect(user1._partition).toEqual("Users");
const currentDate = new Date();
// Create a new Date 30 days from now
const expectedDate = new Date(currentDate.getTime() + thirtyDaysInSeconds * 1000);
expect((user1._expireAt as Date).getTime()).toBeGreaterThanOrEqual(
expectedDate.getTime() - 1000,
);
expect((user1._expireAt as Date).getTime()).toBeLessThanOrEqual(
expectedDate.getTime() + 1000,
);
// test read
const read1 = await db.read(host, origin.id, "Users");
expect(read1.id).toEqual(user1.id);
expect(read1.lastName).toEqual(origin.lastName);
expect((read1._expireAt as Date).getTime()).toBeGreaterThanOrEqual(
expectedDate.getTime() - 1000,
);
expect((read1._expireAt as Date).getTime()).toBeLessThanOrEqual(
expectedDate.getTime() + 1000,
);
// test update
let update1: CosmosDocument = Object.assign({}, origin);
update1.ttl = thirtyDaysInSeconds * 2;
update1 = await db.update(host, update1, "Users");
const expectedDate2 = new Date(currentDate.getTime() + (update1.ttl || 0) * 1000);
expect((update1._expireAt as Date).getTime()).toBeGreaterThanOrEqual(
expectedDate2.getTime() - 1000,
);
expect((update1._expireAt as Date).getTime()).toBeLessThanOrEqual(
expectedDate2.getTime() + 1000,
);
// test upsert
let upsert1: CosmosDocument = Object.assign({}, origin);
upsert1.ttl = thirtyDaysInSeconds * 3;
upsert1 = await db.upsert(host, update1, "Users");
const expectedDate3 = new Date(currentDate.getTime() + (upsert1.ttl || 0) * 1000);
expect((upsert1._expireAt as Date).getTime()).toBeGreaterThanOrEqual(
expectedDate3.getTime() - 1000,
);
expect((upsert1._expireAt as Date).getTime()).toBeLessThanOrEqual(
expectedDate3.getTime() + 1000,
);
} finally {
await db.delete(host, origin.id, "Users");
}
});
});