import { describe, it, expect, vi, beforeAll, beforeEach, afterEach, } from "vitest"; import { ObjectId } from "mongodb"; import * as ConnectionsDal from "../../../src/dal/connections"; import { createConnection } from "../../__testData__/connections"; import { createUser } from "../../__testData__/users"; describe("ConnectionsDal", () => { beforeAll(async () => { await ConnectionsDal.createIndicies(); }); describe("getRequests", () => { it("get by uid", async () => { //GIVEN const uid = new ObjectId().toHexString(); const initOne = await createConnection({ initiatorUid: uid }); const initTwo = await createConnection({ initiatorUid: uid }); const friendOne = await createConnection({ receiverUid: uid }); const _decoy = await createConnection({}); //WHEN / THEM expect( await ConnectionsDal.getConnections({ initiatorUid: uid, receiverUid: uid, }), ).toStrictEqual([initOne, initTwo, friendOne]); }); it("get by uid and status", async () => { //GIVEN const uid = new ObjectId().toHexString(); const initAccepted = await createConnection({ initiatorUid: uid, status: "accepted", }); const _initPending = await createConnection({ initiatorUid: uid, status: "pending", }); const initBlocked = await createConnection({ initiatorUid: uid, status: "blocked", }); const friendAccepted = await createConnection({ receiverUid: uid, status: "accepted", }); const _friendPending = await createConnection({ receiverUid: uid, status: "pending", }); const _decoy = await createConnection({ status: "accepted" }); //WHEN / THEN expect( await ConnectionsDal.getConnections({ initiatorUid: uid, receiverUid: uid, status: ["accepted", "blocked"], }), ).toStrictEqual([initAccepted, initBlocked, friendAccepted]); }); }); describe("create", () => { const now = 1715082588; beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(now); }); afterEach(() => { vi.useRealTimers(); }); it("should fail creating duplicates", async () => { //GIVEN const uid = new ObjectId().toHexString(); const first = await createConnection({ initiatorUid: uid, }); //WHEN/THEN await expect( createConnection({ initiatorUid: first.receiverUid, receiverUid: uid, }), ).rejects.toThrow("Connection request already sent"); }); it("should create", async () => { //GIVEN const uid = new ObjectId().toHexString(); const receiverUid = new ObjectId().toHexString(); //WHEN const created = await ConnectionsDal.create( { uid, name: "Bob" }, { uid: receiverUid, name: "Kevin" }, 2, ); //THEN expect(created).toEqual({ _id: created._id, initiatorUid: uid, initiatorName: "Bob", receiverUid: receiverUid, receiverName: "Kevin", lastModified: now, status: "pending", key: `${uid}/${receiverUid}`, }); }); it("should fail if maximum connections are reached", async () => { //GIVEN const initiatorUid = new ObjectId().toHexString(); await createConnection({ initiatorUid }); await createConnection({ initiatorUid }); //WHEN / THEM await expect(createConnection({ initiatorUid }, 2)).rejects.toThrow( "Maximum number of connections reached\nStack: create connection request", ); }); it("should fail creating if blocked", async () => { //GIVEN const uid = new ObjectId().toHexString(); const first = await createConnection({ initiatorUid: uid, status: "blocked", }); //WHEN/THEN await expect( createConnection({ initiatorUid: first.receiverUid, receiverUid: uid, }), ).rejects.toThrow("Connection blocked"); }); }); describe("updateStatus", () => { const now = 1715082588; beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(now); }); afterEach(() => { vi.useRealTimers(); }); it("should update the status", async () => { //GIVEN const uid = new ObjectId().toHexString(); const first = await createConnection({ receiverUid: uid, lastModified: 100, }); const second = await createConnection({ receiverUid: uid, lastModified: 200, }); //WHEN await ConnectionsDal.updateStatus( uid, first._id.toHexString(), "accepted", ); //THEN expect(await ConnectionsDal.getConnections({ receiverUid: uid })).toEqual( [{ ...first, status: "accepted", lastModified: now }, second], ); //can update twice to the same status await ConnectionsDal.updateStatus( uid, first._id.toHexString(), "accepted", ); }); it("should fail if uid does not match the reeceiverUid", async () => { //GIVEN const uid = new ObjectId().toHexString(); const first = await createConnection({ initiatorUid: uid, }); //WHEN / THEN await expect( ConnectionsDal.updateStatus(uid, first._id.toHexString(), "accepted"), ).rejects.toThrow("No permission or connection not found"); }); }); describe("deleteById", () => { it("should delete by initiator", async () => { //GIVEN const uid = new ObjectId().toHexString(); const first = await createConnection({ initiatorUid: uid, }); const second = await createConnection({ initiatorUid: uid, }); //WHEN await ConnectionsDal.deleteById(uid, first._id.toHexString()); //THEN expect( await ConnectionsDal.getConnections({ initiatorUid: uid }), ).toStrictEqual([second]); }); it("should delete by receiver", async () => { //GIVEN const uid = new ObjectId().toHexString(); const first = await createConnection({ receiverUid: uid, }); const second = await createConnection({ receiverUid: uid, status: "accepted", }); //WHEN await ConnectionsDal.deleteById(uid, first._id.toHexString()); //THEN expect( await ConnectionsDal.getConnections({ initiatorUid: second.initiatorUid, }), ).toStrictEqual([second]); }); it("should fail if uid does not match", async () => { //GIVEN const uid = new ObjectId().toHexString(); const first = await createConnection({ initiatorUid: uid, }); //WHEN / THEN await expect( ConnectionsDal.deleteById("Bob", first._id.toHexString()), ).rejects.toThrow("No permission or connection not found"); }); it("should fail if initiator deletes blocked by receiver", async () => { //GIVEN const uid = new ObjectId().toHexString(); const myRequestWasBlocked = await createConnection({ initiatorName: uid, status: "blocked", }); //WHEN / THEN await expect( ConnectionsDal.deleteById(uid, myRequestWasBlocked._id.toHexString()), ).rejects.toThrow("No permission or connection not found"); }); it("allow receiver to delete blocked", async () => { //GIVEN const uid = new ObjectId().toHexString(); const myBlockedUser = await createConnection({ receiverUid: uid, status: "blocked", }); //WHEN await ConnectionsDal.deleteById(uid, myBlockedUser._id.toHexString()); //THEN expect(await ConnectionsDal.getConnections({ receiverUid: uid })).toEqual( [], ); }); }); describe("deleteByUid", () => { it("should delete by uid", async () => { //GIVEN const uid = new ObjectId().toHexString(); const _initOne = await createConnection({ initiatorUid: uid }); const _initTwo = await createConnection({ initiatorUid: uid }); const _friendOne = await createConnection({ receiverUid: uid }); const decoy = await createConnection({}); //WHEN await ConnectionsDal.deleteByUid(uid); //THEN expect( await ConnectionsDal.getConnections({ initiatorUid: uid, receiverUid: uid, }), ).toEqual([]); expect( await ConnectionsDal.getConnections({ initiatorUid: decoy.initiatorUid, }), ).toEqual([decoy]); }); }); describe("updateName", () => { it("should update the name", async () => { //GIVEN const uid = new ObjectId().toHexString(); const initOne = await createConnection({ initiatorUid: uid, initiatorName: "Bob", }); const initTwo = await createConnection({ initiatorUid: uid, initiatorName: "Bob", }); const friendOne = await createConnection({ receiverUid: uid, receiverName: "Bob", }); const decoy = await createConnection({}); //WHEN await ConnectionsDal.updateName(uid, "King Bob"); //THEN expect( await ConnectionsDal.getConnections({ initiatorUid: uid, receiverUid: uid, }), ).toEqual([ { ...initOne, initiatorName: "King Bob" }, { ...initTwo, initiatorName: "King Bob" }, { ...friendOne, receiverName: "King Bob" }, ]); expect( await ConnectionsDal.getConnections({ initiatorUid: decoy.initiatorUid, }), ).toEqual([decoy]); }); }); describe("getFriendsUids", () => { it("should return friend uids", async () => { //GIVE const uid = new ObjectId().toHexString(); const friendOne = await createConnection({ initiatorUid: uid, status: "accepted", }); const friendTwo = await createConnection({ receiverUid: uid, status: "accepted", }); const friendThree = await createConnection({ receiverUid: uid, status: "accepted", }); const _pending = await createConnection({ initiatorUid: uid, status: "pending", }); const _blocked = await createConnection({ initiatorUid: uid, status: "blocked", }); const _decoy = await createConnection({}); //WHEN const friendUids = await ConnectionsDal.getFriendsUids(uid); //THEN expect(friendUids).toEqual([ uid, friendOne.receiverUid, friendTwo.initiatorUid, friendThree.initiatorUid, ]); }); }); describe("aggregateWithAcceptedConnections", () => { it("should return friend uids", async () => { //GIVE const uid = (await createUser()).uid; const friendOne = await createConnection({ initiatorUid: uid, receiverUid: (await createUser()).uid, status: "accepted", }); const friendTwo = await createConnection({ initiatorUid: (await createUser()).uid, receiverUid: uid, status: "accepted", }); const friendThree = await createConnection({ initiatorUid: (await createUser()).uid, receiverUid: uid, status: "accepted", }); const _pending = await createConnection({ initiatorUid: uid, receiverUid: (await createUser()).uid, status: "pending", }); const _blocked = await createConnection({ initiatorUid: uid, receiverUid: (await createUser()).uid, status: "blocked", }); const _decoy = await createConnection({ receiverUid: (await createUser()).uid, status: "accepted", }); //WHEN const friendUids = await ConnectionsDal.aggregateWithAcceptedConnections<{ uid: string; }>({ collectionName: "users", uid }, [{ $project: { uid: true } }]); //THEN expect(friendUids.flatMap((it) => it.uid).toSorted()).toEqual([ uid, friendOne.receiverUid, friendTwo.initiatorUid, friendThree.initiatorUid, ]); }); it("should return friend uids and metaData", async () => { //GIVE const me = await createUser(); const friend = await createUser(); const connection = await createConnection({ initiatorUid: me.uid, receiverUid: friend.uid, status: "accepted", }); //WHEN const friendUids = await ConnectionsDal.aggregateWithAcceptedConnections( { collectionName: "users", uid: me.uid, includeMetaData: true }, [ { $project: { uid: true, lastModified: "$connectionMeta.lastModified", connectionId: "$connectionMeta._id", }, }, ], ); //THEN expect(friendUids).toEqual([ { _id: friend._id, connectionId: connection._id, lastModified: connection.lastModified, uid: friend.uid, }, { _id: me._id, uid: me.uid, }, ]); }); }); });