From b0c4cdafab80963606916e40a152125bcb427964 Mon Sep 17 00:00:00 2001 From: "M. E. Abdelsalam" Date: Mon, 23 Dec 2024 16:28:58 +0200 Subject: [PATCH 1/2] added: coverage unit tests for redis cache component. --- packages/models/.env.example | 1 + packages/models/.env.test | 1 + packages/models/.env.test.ci | 1 + packages/models/fixtures/cache.ts | 2 + packages/models/tests/cache/call.test.ts | 62 ++++++++++++++++ .../models/tests/cache/onlineStatus.test.ts | 56 ++++++++++++++ packages/models/tests/cache/peer.test.ts | 55 ++++++++++++++ packages/models/tests/cache/rule.test.ts | 73 +++++++++++++++++++ packages/models/tests/cache/tutor.test.ts | 64 ++++++++++++++++ 9 files changed, 315 insertions(+) create mode 100644 packages/models/fixtures/cache.ts create mode 100644 packages/models/tests/cache/call.test.ts create mode 100644 packages/models/tests/cache/onlineStatus.test.ts create mode 100644 packages/models/tests/cache/peer.test.ts create mode 100644 packages/models/tests/cache/rule.test.ts create mode 100644 packages/models/tests/cache/tutor.test.ts diff --git a/packages/models/.env.example b/packages/models/.env.example index b5b892f7c..0750cea49 100644 --- a/packages/models/.env.example +++ b/packages/models/.env.example @@ -4,3 +4,4 @@ PG_PASSWORD='litespace' PG_HOST='0.0.0.0' PG_PORT='5432' PG_DATABASE='litespace' +REDIS_URL=redis://localhost:6379 diff --git a/packages/models/.env.test b/packages/models/.env.test index ff8bf8b86..bb67e65c9 100644 --- a/packages/models/.env.test +++ b/packages/models/.env.test @@ -4,3 +4,4 @@ PG_PASSWORD='litespace' PG_HOST='0.0.0.0' PG_PORT='5432' PG_DATABASE='test' +REDIS_URL=redis://localhost:6379 diff --git a/packages/models/.env.test.ci b/packages/models/.env.test.ci index 3c13dda69..c26a86878 100644 --- a/packages/models/.env.test.ci +++ b/packages/models/.env.test.ci @@ -4,3 +4,4 @@ PG_PASSWORD='litespace' PG_HOST='postgres' PG_PORT='5432' PG_DATABASE='test' +REDIS_URL=redis://redis:6379 diff --git a/packages/models/fixtures/cache.ts b/packages/models/fixtures/cache.ts new file mode 100644 index 000000000..93e415cfb --- /dev/null +++ b/packages/models/fixtures/cache.ts @@ -0,0 +1,2 @@ +import { Cache } from "@litespace/models"; +export const cache = new Cache(process.env.REDIS_URL || ""); diff --git a/packages/models/tests/cache/call.test.ts b/packages/models/tests/cache/call.test.ts new file mode 100644 index 000000000..82558baf0 --- /dev/null +++ b/packages/models/tests/cache/call.test.ts @@ -0,0 +1,62 @@ +import { cache } from "@fixtures/cache"; +import { expect } from "chai"; + +describe("Testing cache/call functions", () => { + beforeAll(async () => { + await cache.connect(); + }); + + afterAll(async () => { + await cache.disconnect(); + }); + + beforeEach(async () => { + await cache.flush(); + }); + + it("should add/retrieve members to/from the cache", async () => { + await cache.call.addMember({ callId: 1, userId: 1 }); + await cache.call.addMember({ callId: 1, userId: 2 }); + await cache.call.addMember({ callId: 2, userId: 3 }); + + const res1 = await cache.call.getMembers(1); + expect(res1).to.have.length(2); + expect(res1).to.contains(1); + expect(res1).to.contains(2); + + const res2 = await cache.call.getMembers(2); + expect(res2).to.have.length(1); + expect(res2).to.contains(3); + }); + + it("should NOT add (duplicate) the same user to the same call twice", async () => { + await cache.call.addMember({ callId: 1, userId: 1 }); + await cache.call.addMember({ callId: 1, userId: 1 }); + + const res = await cache.call.getMembers(1); + expect(res).to.have.length(1); + }); + + it("should remove member from the cache", async () => { + await cache.call.addMember({ callId: 1, userId: 1 }); + await cache.call.addMember({ callId: 1, userId: 2 }); + + await cache.call.removeMemberByUserId(1); + + const res = await cache.call.getMembers(1); + expect(res).to.deep.eq([2]); + }); + + it("should check if a specific member exists in the cache", async () => { + await cache.call.addMember({ callId: 1, userId: 1 }); + await cache.call.addMember({ callId: 1, userId: 2 }); + + const res1 = await cache.call.isMember({ callId: 1, userId: 1 }); + const res2 = await cache.call.isMember({ callId: 1, userId: 3 }); + const res3 = await cache.call.isMember({ callId: 2, userId: 2 }); + + expect(res1).to.true; + expect(res2).to.false; + expect(res3).to.false; + }); +}); diff --git a/packages/models/tests/cache/onlineStatus.test.ts b/packages/models/tests/cache/onlineStatus.test.ts new file mode 100644 index 000000000..a12998a67 --- /dev/null +++ b/packages/models/tests/cache/onlineStatus.test.ts @@ -0,0 +1,56 @@ +import { cache } from "@fixtures/cache"; +import { expect } from "chai"; + +describe("Testing cache/onlineStatus functions", () => { + beforeAll(async () => { + await cache.connect(); + }); + + afterAll(async () => { + await cache.disconnect(); + }); + + beforeEach(async () => { + await cache.flush(); + }); + + it("should add (check if) user (is) in the online collection", async () => { + let res = await cache.onlineStatus.isOnline(1); + expect(res).false; + + await cache.onlineStatus.addUser(1); + + res = await cache.onlineStatus.isOnline(1); + expect(res).true; + }); + + it("should remove user from the online collection", async () => { + await cache.onlineStatus.addUser(1); + await cache.onlineStatus.removeUser(1); + const res = await cache.onlineStatus.isOnline(1); + expect(res).false; + }); + + it("should retrieve all online users", async () => { + await cache.onlineStatus.addUser(1); + await cache.onlineStatus.addUser(2); + await cache.onlineStatus.addUser(3); + + const res = await cache.onlineStatus.getAll(); + expect(res["1"]).to.eq("1"); + expect(res["2"]).to.eq("1"); + expect(res["3"]).to.eq("1"); + }); + + it("should check if a batch of users are online or not", async () => { + await cache.onlineStatus.addUser(1); + await cache.onlineStatus.addUser(2); + await cache.onlineStatus.addUser(3); + + const res = await cache.onlineStatus.isOnlineBatch([1, 2, 3, 4]); + expect(res.get(1)).to.true; + expect(res.get(2)).to.true; + expect(res.get(3)).to.true; + expect(res.get(4)).to.false; + }); +}); diff --git a/packages/models/tests/cache/peer.test.ts b/packages/models/tests/cache/peer.test.ts new file mode 100644 index 000000000..65b3f08b1 --- /dev/null +++ b/packages/models/tests/cache/peer.test.ts @@ -0,0 +1,55 @@ +import { cache } from "@fixtures/cache"; +import { expect } from "chai"; + +describe("Testing cache/peer functions", () => { + beforeAll(async () => { + await cache.connect(); + }); + + afterAll(async () => { + await cache.disconnect(); + }); + + beforeEach(async () => { + await cache.flush(); + }); + + it("should set/retrieve user peer id in/from the cache", async () => { + await cache.peer.setUserPeerId(1, "testing"); + const res = await cache.peer.getUserPeerId(1); + expect(res).to.eq("testing"); + }); + + it("should set/retrieve ghost peer id in/from the cache", async () => { + await cache.peer.setGhostPeerId(1, "testing"); + const res = await cache.peer.getGhostPeerId(1); + expect(res).to.eq("testing"); + }); + + it("should NOT exist overridding between the ghost and the user", async () => { + await cache.peer.setGhostPeerId(1, "dump"); + // checking if user overrides the ghost or vice versa + await cache.peer.setUserPeerId(1, "user testing"); + await cache.peer.setGhostPeerId(1, "ghost testing"); + + const res1 = await cache.peer.getUserPeerId(1); + const res2 = await cache.peer.getGhostPeerId(1); + + expect(res1).to.eq("user testing"); + expect(res2).to.eq("ghost testing"); + }); + + it("should remove user peer id from the cache", async () => { + await cache.peer.setUserPeerId(1, "testing"); + await cache.peer.removeUserPeerId(1); + const res = await cache.peer.getUserPeerId(1); + expect(res).to.eq(null); + }); + + it("should remove ghost peer id from the cache", async () => { + await cache.peer.setGhostPeerId(1, "testing"); + await cache.peer.removeGhostPeerId(1); + const res = await cache.peer.getGhostPeerId(1); + expect(res).to.eq(null); + }); +}); diff --git a/packages/models/tests/cache/rule.test.ts b/packages/models/tests/cache/rule.test.ts new file mode 100644 index 000000000..c6ecd35c0 --- /dev/null +++ b/packages/models/tests/cache/rule.test.ts @@ -0,0 +1,73 @@ +import { cache } from "@fixtures/cache"; +import { expect } from "chai"; +import dayjs from "dayjs"; +import { first } from "lodash"; + +describe("Testing cache/rules functions", () => { + beforeAll(async () => { + await cache.connect(); + }); + + afterAll(async () => { + await cache.disconnect(); + }); + + beforeEach(async () => { + await cache.flush(); + }); + + it("should set/retreive one rule in/from the cache", async () => { + await cache.rules.setOne({ tutor: 1, rule: 1, events: [] }) + let res = await cache.rules.getOne({ tutor: 1, rule: 1 }) + expect(res).to.have.length(0); + + const mockRuleEvent = { + id: 1, + start: dayjs().toISOString(), + end: dayjs().add(1, "hour").toISOString(), + } + + await cache.rules.setOne({ tutor: 1, rule: 1, events: [mockRuleEvent] }); + res = await cache.rules.getOne({ tutor: 1, rule: 1 }) + expect(res).to.have.length(1); + expect(first(res)).to.deep.eq(mockRuleEvent); + }); + + it("should set/retreive many rules in/from the cache", async () => { + const mockRuleEvents = [ + { + id: 1, + start: dayjs().toISOString(), + end: dayjs().add(1, "hour").toISOString(), + }, + { + id: 2, + start: dayjs().add(1, "day").toISOString(), + end: dayjs().add(2, "days").toISOString(), + }, + ] + + const mock1 = { tutor: 1, rule: 1, events: [mockRuleEvents[0]] }; + const mock2 = { tutor: 2, rule: 2, events: [mockRuleEvents[1]] }; + + await cache.rules.setMany([mock1, mock2]); + + const res = await cache.rules.getAll() + expect(res).to.have.length(2); + expect(res).to.deep.contains(mock1); + expect(res).to.deep.contains(mock2); + }); + + it("should remove one rule from the cache", async () => { + await cache.rules.setOne({ tutor: 1, rule: 1, events: [] }) + await cache.rules.deleteOne({ tutor: 1, rule: 1 }); + const res = await cache.rules.getOne({ tutor: 1, rule: 1 }); + expect(res).to.eq(null); + }); + + it("should check if rules key does exist in the cache", async () => { + expect(await cache.rules.exists()).to.false; + await cache.rules.setOne({ tutor: 1, rule: 1, events: [] }) + expect(await cache.rules.exists()).to.true; + }); +}); diff --git a/packages/models/tests/cache/tutor.test.ts b/packages/models/tests/cache/tutor.test.ts new file mode 100644 index 000000000..50c3f1e0f --- /dev/null +++ b/packages/models/tests/cache/tutor.test.ts @@ -0,0 +1,64 @@ +import { cache } from "@fixtures/cache"; +import { faker } from "@faker-js/faker/locale/ar"; +import { IUser } from "@litespace/types"; +import { expect } from "chai"; +import { sample } from "lodash"; + +const getMockTutorCache = (id: number) => ({ + id, + name: faker.word.noun(), + image: faker.internet.url(), + video: faker.internet.url(), + bio: faker.lorem.words(30), + about: faker.lorem.sentence(5), + gender: sample([IUser.Gender.Male, IUser.Gender.Female]), + notice: faker.number.int(), + topics: faker.lorem.words(5).split(" "), + avgRating: faker.number.float(), + studentCount: faker.number.int(), + lessonCount: faker.number.int(), +}) + +describe("Testing cache/tutors functions", () => { + beforeAll(async () => { + await cache.connect(); + }); + + afterAll(async () => { + await cache.disconnect(); + }); + + beforeEach(async () => { + await cache.flush(); + }); + + it("should set/retreive one tutor in/from the cache", async () => { + const mockData = getMockTutorCache(1); + await cache.tutors.setOne(mockData); + const res = await cache.tutors.getOne(1); + expect(res).to.deep.eq(mockData); + }); + + it("should set/retreive many tutors in/from the cache", async () => { + const tutor1 = getMockTutorCache(1); + const tutor2 = getMockTutorCache(2); + + await cache.tutors.setMany([tutor1, tutor2]); + + const res = await cache.tutors.getAll(); + expect(res).to.have.length(2); + expect(res).to.deep.contains(tutor1); + expect(res).to.deep.contains(tutor2); + }); + + it("should check if tutors key exists in the cache or not", async () => { + const res1 = await cache.tutors.exists(); + expect(res1).to.false; + + const mockData = getMockTutorCache(1); + await cache.tutors.setOne(mockData); + + const res2 = await cache.tutors.exists(); + expect(res2).to.true; + }); +}); From 9ce9716f49058ad08dffb0994308db2f6fe9a887 Mon Sep 17 00:00:00 2001 From: "M. E. Abdelsalam" Date: Mon, 23 Dec 2024 18:46:05 +0200 Subject: [PATCH 2/2] fix: a tedious error (in indeterministic unit test) has been fixed. --- packages/models/tests/topics.test.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/models/tests/topics.test.ts b/packages/models/tests/topics.test.ts index 5a1f3d32e..1984c9bc2 100644 --- a/packages/models/tests/topics.test.ts +++ b/packages/models/tests/topics.test.ts @@ -142,8 +142,18 @@ describe("Topics", () => { describe(nameof(topics.isExistsBatch), () => { it("should return a map that tells if the passed topic ids exists in the db or not.", async () => { - const topic1 = await fixtures.topic(); - const topic2 = await fixtures.topic(); + const topic1 = await fixtures.topic({ + name: { + ar: "dump-topic-name-ar", + en: "dump-topic-name-en", + } + }); + const topic2 = await fixtures.topic({ + name: { + ar: "another-dump-topic-name-ar", + en: "another-dump-topic-name-en", + } + }); const list = [topic1.id, topic2.id, 123]; const existanceMap = await topics.isExistsBatch(list);