Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 36 additions & 41 deletions models/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,59 +138,54 @@ const updateApplication = async (dataToUpdate: object, applicationId: string) =>
};

const nudgeApplication = async ({ applicationId, userId }: { applicationId: string; userId: string }) => {
try {
const currentTime = Date.now();
const twentyFourHoursInMilliseconds = convertDaysToMilliseconds(1);
const currentTime = Date.now();
const twentyFourHoursInMilliseconds = convertDaysToMilliseconds(1);

const result = await firestore.runTransaction(async (transaction) => {
const applicationRef = ApplicationsModel.doc(applicationId);
const applicationDoc = await transaction.get(applicationRef);
const result = await firestore.runTransaction(async (transaction) => {
const applicationRef = ApplicationsModel.doc(applicationId);
const applicationDoc = await transaction.get(applicationRef);

if (!applicationDoc.exists) {
return { status: NUDGE_APPLICATION_STATUS.notFound };
}
if (!applicationDoc.exists) {
return { status: NUDGE_APPLICATION_STATUS.notFound };
}

const application = applicationDoc.data();
const application = applicationDoc.data();

if (application.userId !== userId) {
return { status: NUDGE_APPLICATION_STATUS.unauthorized };
}
if (application.userId !== userId) {
return { status: NUDGE_APPLICATION_STATUS.unauthorized };
}

if (application.status !== APPLICATION_STATUS_TYPES.PENDING) {
return { status: NUDGE_APPLICATION_STATUS.notPending };
}
if (application.status !== APPLICATION_STATUS_TYPES.PENDING) {
return { status: NUDGE_APPLICATION_STATUS.notPending };
}

const lastNudgeAt = application.lastNudgeAt;
if (lastNudgeAt) {
const lastNudgeTimestamp = new Date(lastNudgeAt).getTime();
const timeDifference = currentTime - lastNudgeTimestamp;
const lastNudgeAt = application.lastNudgeAt;
if (lastNudgeAt) {
const lastNudgeTimestamp = new Date(lastNudgeAt).getTime();
const timeDifference = currentTime - lastNudgeTimestamp;

if (timeDifference <= twentyFourHoursInMilliseconds) {
return { status: NUDGE_APPLICATION_STATUS.tooSoon };
}
if (timeDifference <= twentyFourHoursInMilliseconds) {
return { status: NUDGE_APPLICATION_STATUS.tooSoon };
}
}

const currentNudgeCount = application.nudgeCount || 0;
const updatedNudgeCount = currentNudgeCount + 1;
const newLastNudgeAt = new Date(currentTime).toISOString();

transaction.update(applicationRef, {
nudgeCount: updatedNudgeCount,
lastNudgeAt: newLastNudgeAt,
});
const currentNudgeCount = application.nudgeCount || 0;
const updatedNudgeCount = currentNudgeCount + 1;
const newLastNudgeAt = new Date(currentTime).toISOString();

return {
status: NUDGE_APPLICATION_STATUS.success,
nudgeCount: updatedNudgeCount,
lastNudgeAt: newLastNudgeAt,
};
transaction.update(applicationRef, {
nudgeCount: updatedNudgeCount,
lastNudgeAt: newLastNudgeAt,
});

return result;
} catch (err) {
logger.error("Error while nudging application", err);
throw err;
}
return {
status: NUDGE_APPLICATION_STATUS.success,
nudgeCount: updatedNudgeCount,
lastNudgeAt: newLastNudgeAt,
};
});

return result;
};

module.exports = {
Expand Down
8 changes: 6 additions & 2 deletions services/logService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ interface LogBody {
* @param meta { LogMeta }: Meta data of the log
* @param body { LogBody }: Body of the log
*/
export const addLog = async (type: string, meta: LogMeta, body: LogBody): Promise<FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData>> => {
export const addLog = async (
type: string,
meta: LogMeta,
body: LogBody
): Promise<FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData>> => {
try {
const log = {
type,
Expand All @@ -32,4 +36,4 @@ export const addLog = async (type: string, meta: LogMeta, body: LogBody): Promis
logger.error("Error in adding log", err);
throw new Error(INTERNAL_SERVER_ERROR);
}
};
};
151 changes: 149 additions & 2 deletions test/integration/application.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import chai from "chai";
import chaiHttp from "chai-http";
const { expect } = chai;
import config from "config";
import sinon from "sinon";
const app = require("../../server");
const addUser = require("../utils/addUser");
const cleanDb = require("../utils/cleanDb");
const authService = require("../../services/authService");
const userData = require("../fixtures/user/user")();
const applicationModel = require("../../models/applications");
const { requestRoleData } = require("../fixtures/discordactions/discordactions");

const applicationsData = require("../fixtures/applications/applications")();
const cookieName = config.get("userToken.cookieName");
const { getUserApplicationObject } = require("../../utils/application");
const { APPLICATION_ERROR_MESSAGES, API_RESPONSE_MESSAGES } = require("../../constants/application");

const appOwner = userData[3];
const superUser = userData[4];
Expand Down Expand Up @@ -65,6 +65,7 @@ describe("Application", function () {

after(async function () {
await cleanDb();
sinon.restore();
});

describe("GET /applications", function () {
Expand Down Expand Up @@ -488,4 +489,150 @@ describe("Application", function () {
});
});
});

describe("PATCH /applications/:applicationId/nudge", function () {
let nudgeApplicationId: string;

beforeEach(async function () {
const applicationData = { ...applicationsData[0], userId };
nudgeApplicationId = await applicationModel.addApplication(applicationData);
});

afterEach(async function () {
sinon.restore();
});

it("should successfully nudge a pending application when user owns it and no previous nudge exists", function (done) {
chai
.request(app)
.patch(`/applications/${nudgeApplicationId}/nudge`)
.set("cookie", `${cookieName}=${jwt}`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(200);
expect(res.body.message).to.be.equal(API_RESPONSE_MESSAGES.NUDGE_SUCCESS);
expect(res.body.nudgeCount).to.be.equal(1);
expect(res.body.lastNudgeAt).to.be.a("string");
done();
});
});

it("should successfully nudge an application when 24 hours have passed since last nudge", function (done) {
chai
.request(app)
.patch(`/applications/${nudgeApplicationId}/nudge`)
.set("cookie", `${cookieName}=${jwt}`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(200);
expect(res.body.nudgeCount).to.be.equal(1);

const twentyFiveHoursAgo = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
applicationModel.updateApplication({ lastNudgeAt: twentyFiveHoursAgo }, nudgeApplicationId).then(() => {
chai
.request(app)
.patch(`/applications/${nudgeApplicationId}/nudge`)
.set("cookie", `${cookieName}=${jwt}`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(200);
expect(res.body.message).to.be.equal(API_RESPONSE_MESSAGES.NUDGE_SUCCESS);
expect(res.body.nudgeCount).to.be.equal(2);
expect(res.body.lastNudgeAt).to.be.a("string");
done();
});
});
});
});

it("should return 404 if the application doesn't exist", function (done) {
chai
.request(app)
.patch(`/applications/non-existent-id/nudge`)
.set("cookie", `${cookieName}=${jwt}`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(404);
expect(res.body.error).to.be.equal("Not Found");
expect(res.body.message).to.be.equal("Application not found");
done();
});
});

it("should return 401 if user is not authenticated", function (done) {
chai
.request(app)
.patch(`/applications/${nudgeApplicationId}/nudge`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(401);
expect(res.body.error).to.be.equal("Unauthorized");
expect(res.body.message).to.be.equal("Unauthenticated User");
done();
});
});

it("should return 401 if user does not own the application", function (done) {
chai
.request(app)
.patch(`/applications/${nudgeApplicationId}/nudge`)
.set("cookie", `${cookieName}=${secondUserJwt}`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(401);
expect(res.body.error).to.be.equal("Unauthorized");
expect(res.body.message).to.be.equal("You are not authorized to nudge this application");
done();
});
});

it("should return 429 when trying to nudge within 24 hours", function (done) {
chai
.request(app)
.patch(`/applications/${nudgeApplicationId}/nudge`)
.set("cookie", `${cookieName}=${jwt}`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(200);

chai
.request(app)
.patch(`/applications/${nudgeApplicationId}/nudge`)
.set("cookie", `${cookieName}=${jwt}`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(429);
expect(res.body.error).to.be.equal("Too Many Requests");
expect(res.body.message).to.be.equal(APPLICATION_ERROR_MESSAGES.NUDGE_TOO_SOON);
done();
});
});
});

it("should return 400 when trying to nudge an application that is not in pending status", function (done) {
const nonPendingApplicationData = { ...applicationsData[1], userId };
applicationModel.addApplication(nonPendingApplicationData).then((nonPendingApplicationId: string) => {
chai
.request(app)
.patch(`/applications/${nonPendingApplicationId}/nudge`)
.set("cookie", `${cookieName}=${jwt}`)
.end(function (err, res) {
if (err) return done(err);

expect(res).to.have.status(400);
expect(res.body.error).to.be.equal("Bad Request");
expect(res.body.message).to.be.equal(APPLICATION_ERROR_MESSAGES.NUDGE_ONLY_PENDING_ALLOWED);
done();
});
});
});
});
});
Loading