From a9bdb4dca8b130c38872664454a8bd7aa53be7a5 Mon Sep 17 00:00:00 2001 From: Achintya Chatterjee <55826451+Achintya-Chatterjee@users.noreply.github.com> Date: Tue, 24 Dec 2024 22:10:03 +0530 Subject: [PATCH] test: added test for lazy load discord groups page (#2310) * feat: added pagination for lazy loading in the /groups route to load the discord groups asynchrounously instead of all at once * feat: Implement pagination for lazy loading in - Added feature-flag-based () lazy loading to . - Introduced support for , , and __TEXT __DATA __OBJC others dec hex query parameters for pagination. - Validated query parameters and returned structured pagination metadata (, __TEXT __DATA __OBJC others dec hex, , ). - Updated response structure to include enriched group membership information. - Modified test cases in : - Added tests for cursor-based lazy loading behavior. - Handled scenarios with and without cursors. - Verified error handling for database query failures. * fix: test cases * fix: correct paginated group roles endpoint and dynamic link generation - Fixed incorrect and link generation in the endpoint. - Ensured dynamically includes the correct path (). - Dynamically constructed and links using and . - Refactored function: - Simplified conditional logic for feature flag. - Added error handling for invalid page and size parameters. - Removed hardcoding of URLs in response links to make them adaptive to the environment. - Improved logging for easier debugging when issues arise. - Added handling to ensure old behavior (non-dev mode) works as expected. * test: added test for the validateLazyLoadingParams * chore remove test from here as moving tests in a different PR * fix: failing tests * test: added test for getPaginatedGroupRolesByPage model functions * test: add tests for getPaginatedAllGroupRoles function in the controller * chore: remove comments from the code --------- Co-authored-by: Vikas Singh <59792866+vikasosmium@users.noreply.github.com> --- test/integration/discordactions.test.js | 129 ++++++++++++++++++ .../discordactions-validators.test.js | 70 ++++++++-- test/unit/models/discordactions.test.js | 44 ++++++ 3 files changed, 233 insertions(+), 10 deletions(-) diff --git a/test/integration/discordactions.test.js b/test/integration/discordactions.test.js index 574ea4858..d709a7ec5 100644 --- a/test/integration/discordactions.test.js +++ b/test/integration/discordactions.test.js @@ -1218,4 +1218,133 @@ describe("Discord actions", function () { }); }); }); + + describe("GET /discord-actions/groups (getPaginatedAllGroupRoles)", function () { + let userId; + let userAuthToken; + + beforeEach(async function () { + const user = await addUser(userData[0]); + userId = user; + userAuthToken = authService.generateAuthToken({ userId }); + + await discordRoleModel.add(groupData[0]); + await discordRoleModel.add(groupData[1]); + }); + + afterEach(async function () { + sinon.restore(); + await cleanDb(); + }); + + it("should return paginated results when dev=true is passed", function (done) { + chai + .request(app) + .get("/discord-actions/groups?dev=true&page=1&size=10") + .set("cookie", `${cookieName}=${userAuthToken}`) + .end((err, res) => { + if (err) { + return done(err); + } + + expect(res).to.have.status(200); + expect(res.body).to.be.an("object"); + expect(res.body.message).to.equal("Roles fetched successfully!"); + expect(res.body.groups).to.be.an("array"); + + const groups = res.body.groups; + groups.forEach((group) => { + expect(group).to.have.keys([ + "roleid", + "rolename", + "memberCount", + "firstName", + "lastName", + "image", + "isMember", + ]); + }); + + expect(res.body.links).to.have.keys(["next", "prev"]); + return done(); + }); + }); + + it("should return null for next link on the last page", function (done) { + const size = 10; + const page = 2; + + chai + .request(app) + .get(`/discord-actions/groups?dev=true&page=${page}&size=${size}`) + .set("cookie", `${cookieName}=${userAuthToken}`) + .end((err, res) => { + if (err) { + return done(err); + } + + expect(res).to.have.status(200); + expect(res.body).to.be.an("object"); + expect(res.body.message).to.equal("Roles fetched successfully!"); + expect(res.body.groups).to.be.an("array"); + expect(res.body.links).to.have.keys(["next", "prev"]); + // eslint-disable-next-line no-unused-expressions + expect(res.body.links.next).to.be.null; + expect(res.body.links.prev).to.equal(`/discord-actions/groups?page=${page - 1}&size=${size}&dev=true`); + return done(); + }); + }); + + it("should return a bad request error for invalid size parameter", function (done) { + chai + .request(app) + .get("/discord-actions/groups?dev=true&size=101&page=1") + .set("cookie", `${cookieName}=${userAuthToken}`) + .end((_err, res) => { + expect(res).to.have.status(400); + expect(res.body).to.be.an("object"); + expect(res.body.message).to.equal('"size" must be less than or equal to 100'); + return done(); + }); + }); + + it("should return an empty array for groups on a page with no data", function (done) { + const size = 10; + const page = 100; + + chai + .request(app) + .get(`/discord-actions/groups?dev=true&page=${page}&size=${size}`) + .set("cookie", `${cookieName}=${userAuthToken}`) + .end((_err, res) => { + expect(res).to.have.status(200); + expect(res.body).to.be.an("object"); + expect(res.body.message).to.equal("Roles fetched successfully!"); + // eslint-disable-next-line no-unused-expressions + expect(res.body.groups).to.be.an("array").that.is.empty; + expect(res.body.links).to.have.keys(["next", "prev"]); + // eslint-disable-next-line no-unused-expressions + expect(res.body.links.next).to.be.null; + expect(res.body.links.prev).to.equal(`/discord-actions/groups?page=${page - 1}&size=${size}&dev=true`); + return done(); + }); + }); + + it("should handle internal server errors", function (done) { + sinon.stub(discordRolesModel, "getPaginatedGroupRolesByPage").throws(new Error("Database error")); + + chai + .request(app) + .get("/discord-actions/groups?dev=true") + .set("cookie", `${cookieName}=${userAuthToken}`) + // eslint-disable-next-line node/handle-callback-err + .end((err, res) => { + expect(res).to.have.status(500); + expect(res.body).to.be.an("object"); + expect(res.body.message).to.equal("An internal server error occurred"); + sinon.restore(); + return done(); + }); + }); + }); }); diff --git a/test/unit/middlewares/discordactions-validators.test.js b/test/unit/middlewares/discordactions-validators.test.js index b96804bc5..de2bed343 100644 --- a/test/unit/middlewares/discordactions-validators.test.js +++ b/test/unit/middlewares/discordactions-validators.test.js @@ -3,6 +3,7 @@ const { validateGroupRoleBody, validateMemberRoleBody, validateUpdateUsersNicknameStatusBody, + validateLazyLoadingParams, } = require("../../../middlewares/validators/discordactions"); const { expect } = require("chai"); @@ -20,7 +21,7 @@ describe("Middleware | Validators | discord actions", function () { expect(nextSpy.calledOnce).to.be.equal(true); }); - it("stops the propogation of the event to next function", async function () { + it("stops the propagation of the event to next function", async function () { const res = { boom: { badRequest: () => {}, @@ -51,7 +52,7 @@ describe("Middleware | Validators | discord actions", function () { expect(nextSpy.calledOnce).to.be.equal(true); }); - it("stops the propogation to the next function", async function () { + it("stops the propagation to the next function", async function () { const res = { boom: { badRequest: () => {}, @@ -108,23 +109,72 @@ describe("Middleware | Validators | discord actions", function () { }); expect(nextSpy.callCount).to.be.equal(0); }); + }); + + describe("validateLazyLoadingParams", function () { + it("should pass the request to the next function when valid params are provided", async function () { + const req = { + query: { + page: 1, + size: 10, + dev: "true", + }, + }; + const nextSpy = Sinon.spy(); + const res = {}; + await validateLazyLoadingParams(req, res, nextSpy); + expect(nextSpy.calledOnce).to.be.equal(true); + }); - it("should throw error when the lastNicknameUpdate timestamp is not a string or timestamp", async function () { + it("should return a bad request error when size is out of range", async function () { const res = { boom: { - badRequest: () => {}, + badRequest: Sinon.spy(), + }, + }; + const req = { + query: { + size: 200, }, }; const nextSpy = Sinon.spy(); + await validateLazyLoadingParams(req, res, nextSpy); + expect(nextSpy.called).to.be.equal(false); + expect(res.boom.badRequest.calledOnce).to.be.equal(true); + }); + + it("should return a bad request error when page is negative", async function () { + const res = { + boom: { + badRequest: Sinon.spy(), + }, + }; const req = { - body: { - lastNicknameUpdate: 112.45478, + query: { + page: -1, }, }; - await validateMemberRoleBody(req, res, nextSpy).catch((err) => { - expect(err).to.be.an.instanceOf(Error); - }); - expect(nextSpy.callCount).to.be.equal(0); + const nextSpy = Sinon.spy(); + await validateLazyLoadingParams(req, res, nextSpy); + expect(nextSpy.called).to.be.equal(false); + expect(res.boom.badRequest.calledOnce).to.be.equal(true); + }); + + it("should return a bad request error when dev has an invalid value", async function () { + const res = { + boom: { + badRequest: Sinon.spy(), + }, + }; + const req = { + query: { + dev: "invalid", + }, + }; + const nextSpy = Sinon.spy(); + await validateLazyLoadingParams(req, res, nextSpy); + expect(nextSpy.called).to.be.equal(false); + expect(res.boom.badRequest.calledOnce).to.be.equal(true); }); }); }); diff --git a/test/unit/models/discordactions.test.js b/test/unit/models/discordactions.test.js index 249e70601..00d92793f 100644 --- a/test/unit/models/discordactions.test.js +++ b/test/unit/models/discordactions.test.js @@ -20,6 +20,7 @@ const tasksModel = firestore.collection("tasks"); const { createNewRole, getAllGroupRoles, + getPaginatedGroupRolesByPage, isGroupRoleExists, addGroupRoleToMember, deleteRoleFromDatabase, @@ -1299,4 +1300,47 @@ describe("discordactions", function () { } }); }); + + describe("getPaginatedGroupRolesByPage", function () { + let orderByStub, offsetStub, limitStub, getStub; + + beforeEach(function () { + orderByStub = sinon.stub(); + offsetStub = sinon.stub(); + limitStub = sinon.stub(); + getStub = sinon.stub(); + + orderByStub.returns({ offset: offsetStub }); + offsetStub.returns({ limit: limitStub }); + limitStub.returns({ get: getStub }); + + sinon.stub(discordRoleModel, "orderBy").returns(orderByStub); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should return an empty array if no roles are found", async function () { + getStub.resolves({ docs: [] }); + + const result = await getPaginatedGroupRolesByPage({ offset: 0, limit: 10 }); + + expect(result).to.deep.equal({ + roles: [], + total: 0, + }); + }); + + it("should throw an error if a database error occurs", async function () { + getStub.rejects(new Error("Database error")); + + try { + await getPaginatedGroupRolesByPage({ offset: 0, limit: 10 }); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.equal("Database error while paginating group roles"); + } + }); + }); });