Skip to content
Open
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
44 changes: 21 additions & 23 deletions models/userStatus.js
Comment thread
MayankBansal12 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const usersCollection = firestore.collection("users");
const config = require("config");
const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl");
const { generateAuthTokenForCloudflare } = require("../utils/discord-actions");
const logger = require("../utils/logger");
const { BATCH_SIZE_IN_CLAUSE } = require("../constants/firebase");

// added this function here to avoid circular dependency
Expand Down Expand Up @@ -265,10 +266,13 @@ const updateAllUserStatus = async () => {
nonOooUsersUnaltered: 0,
};
try {
const userStatusDocs = await userStatusModel.where("futureStatus.state", "in", ["ACTIVE", "IDLE", "OOO"]).get();
summary.usersCount = userStatusDocs._size;
const today = Date.now();
const userStatusDocs = await userStatusModel
.where("futureStatus.state", "in", ["ACTIVE", "IDLE", "OOO"])
.where("futureStatus.from", "<=", today)
.get();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
summary.usersCount = userStatusDocs.size;
const batch = firestore.batch();
const today = new Date().getTime();
for (const document of userStatusDocs.docs) {
const doc = document.data();
const docRef = document.ref;
Expand All @@ -280,32 +284,28 @@ const updateAllUserStatus = async () => {
const currentState = currentStatus?.state;
const currentUntil = currentStatus?.until;
if (futureState === "ACTIVE" || futureState === "IDLE") {
if (today >= futureStatus.from) {
// OOO period is over and we need to update their current status
newStatusData.currentStatus = { ...futureStatus, until: "", updatedAt: today };
delete newStatusData.futureStatus;
const lastOooUntilUpdate = resolveLastOooUntil({
previousState: currentState,
previousUntil: currentUntil,
nextState: futureState,
fallbackTimestamp: today,
});
if (lastOooUntilUpdate !== undefined) {
newStatusData.lastOooUntil = lastOooUntilUpdate;
}
toUpdate = !toUpdate;
summary.oooUsersAltered++;
} else {
summary.oooUsersUnaltered++;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously there was oooUsersUnaltered for keeping track of status that aren't updated, are we still using it anywhere for OOO users who's status aren't changed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we are still using it check line 296

// OOO period is over and we need to update their current status
newStatusData.currentStatus = { ...futureStatus, until: "", updatedAt: today };
delete newStatusData.futureStatus;
const lastOooUntilUpdate = resolveLastOooUntil({
previousState: currentState,
previousUntil: currentUntil,
nextState: futureState,
fallbackTimestamp: today,
});
if (lastOooUntilUpdate !== undefined) {
newStatusData.lastOooUntil = lastOooUntilUpdate;
}
toUpdate = !toUpdate;
summary.oooUsersAltered++;
} else {
// futureState is OOO
if (today > futureStatus.until) {
// the OOO period is over
delete newStatusData.futureStatus;
toUpdate = !toUpdate;
summary.nonOooUsersAltered++;
} else if (today <= doc.futureStatus.until && today >= doc.futureStatus.from) {
} else {
// the current date i.e today lies in between the from and until so we need to swap the status
let newCurrentStatus = {};
let newFutureStatus = {};
Expand All @@ -318,8 +318,6 @@ const updateAllUserStatus = async () => {
newStatusData.lastOooUntil = null;
toUpdate = !toUpdate;
summary.nonOooUsersAltered++;
} else {
summary.nonOooUsersUnaltered++;
}
}
if (toUpdate) {
Expand Down
34 changes: 34 additions & 0 deletions test/fixtures/userStatus/userStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,39 @@ const generateDefaultFutureStatus = (state, from, until) => {
return futureStatusData;
};

const generateOooUserStatusDoc = (
userId,
today,
{
currentStatusFromOffset = -24 * 60 * 60 * 1000,
currentStatusUntilOffset = 2 * 24 * 60 * 60 * 1000,
futureStatusFromOffset,
} = {}
) => {
const currentFrom = today + currentStatusFromOffset;
return {
userId,
currentStatus: {
state: userState.OOO,
from: currentFrom,
until: today + currentStatusUntilOffset,
message: "On leave",
updatedAt: currentFrom,
},
futureStatus: {
state: userState.ACTIVE,
from: today + futureStatusFromOffset,
until: "",
message: "",
updatedAt: today - 24 * 60 * 60 * 1000,
},
monthlyHours: {
committed: 40,
updatedAt: currentFrom,
},
};
};

module.exports = {
userStatusDataForNewUser,
userStatusDataAfterSignup,
Expand All @@ -239,4 +272,5 @@ module.exports = {
inputFixtureForFnConvertTimestampsToUTC,
OutputFixtureForFnConvertTimestampsToUTC,
generateDefaultFutureStatus,
generateOooUserStatusDoc,
};
92 changes: 90 additions & 2 deletions test/integration/userStatus.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ const app = require("../../server");
const authService = require("../../services/authService");
const addUser = require("../utils/addUser");
const cleanDb = require("../utils/cleanDb");
// Import fixtures
const userData = require("../fixtures/user/user")();
const superUser = userData[4];
const {
userStatusDataForNewUser,
userStatusDataForOooState,
generateUserStatusData,
generateOooUserStatusDoc,
} = require("../fixtures/userStatus/userStatus");

const config = require("config");
Expand Down Expand Up @@ -183,6 +183,26 @@ describe("UserStatus", function () {
});

describe("PATCH /users/status/update", function () {
let clock;

beforeEach(function () {
clock = sinon.useFakeTimers({
now: new Date("2026-07-14T02:00:00.000Z").getTime(),
toFake: ["Date"],
});
});

afterEach(function () {
clock.restore();
});

const setupOooUserStatus = async (userFixture, today, offsets) => {
const userId = await addUser(userFixture);
const docRef = firestore.collection("usersStatus").doc();
await docRef.set(generateOooUserStatusDoc(userId, today, offsets));
return { userId, docRef };
};

it("Should return 401 for unauthorized request", async function () {
const response = await chai.request(app).patch("/users/status/update");
expect(response).to.have.status(401);
Expand All @@ -192,6 +212,75 @@ describe("UserStatus", function () {
const response = await chai.request(app).patch("/users/status/update").set("cookie", `${cookieName}=${jwt}`);
expect(response).to.have.status(401);
});

it("Should transition OOO → ACTIVE when futureStatus.from is in the past", async function () {
const today = Date.now();
const { docRef } = await setupOooUserStatus(userData[1], today, {
currentStatusFromOffset: -2 * 24 * 60 * 60 * 1000,
currentStatusUntilOffset: 2 * 24 * 60 * 60 * 1000,
futureStatusFromOffset: -24 * 60 * 60 * 1000,
});

const response = await chai
.request(app)
.patch("/users/status/update")
.set("cookie", `${cookieName}=${superUserAuthToken}`);

expect(response).to.have.status(200);
expect(response.body.message).to.equal("All User Status updated successfully.");
expect(response.body.data.usersCount).to.equal(1);
expect(response.body.data.oooUsersAltered).to.equal(1);

const updatedDoc = await docRef.get();
expect(updatedDoc.data().currentStatus.state).to.equal(userState.ACTIVE);
expect(updatedDoc.data().futureStatus).to.equal(undefined);
});

it("Should NOT update status when futureStatus.from is in the future", async function () {
const today = Date.now();
const { docRef } = await setupOooUserStatus(userData[2], today, {
currentStatusFromOffset: -24 * 60 * 60 * 1000,
currentStatusUntilOffset: 2 * 24 * 60 * 60 * 1000,
futureStatusFromOffset: 24 * 60 * 60 * 1000,
});

const response = await chai
.request(app)
.patch("/users/status/update")
.set("cookie", `${cookieName}=${superUserAuthToken}`);

expect(response).to.have.status(200);
expect(response.body.message).to.equal("All User Status updated successfully.");
expect(response.body.data.usersCount).to.equal(0);
expect(response.body.data.oooUsersAltered).to.equal(0);

const doc = await docRef.get();
expect(doc.data().currentStatus.state).to.equal(userState.OOO);
expect(doc.data().futureStatus.state).to.equal(userState.ACTIVE);
});

it("Should transition OOO → ACTIVE when futureStatus.from === today (boundary)", async function () {
const today = Date.now();
const { docRef } = await setupOooUserStatus(userData[3], today, {
currentStatusFromOffset: -24 * 60 * 60 * 1000,
currentStatusUntilOffset: 24 * 60 * 60 * 1000,
futureStatusFromOffset: 0,
});

const response = await chai
.request(app)
.patch("/users/status/update")
.set("cookie", `${cookieName}=${superUserAuthToken}`);

expect(response).to.have.status(200);
expect(response.body.message).to.equal("All User Status updated successfully.");
expect(response.body.data.usersCount).to.equal(1);
expect(response.body.data.oooUsersAltered).to.equal(1);

const updatedDoc = await docRef.get();
expect(updatedDoc.data().currentStatus.state).to.equal(userState.ACTIVE);
expect(updatedDoc.data().futureStatus).to.equal(undefined);
});
});

describe("PATCH /users/status/:userid", function () {
Expand Down Expand Up @@ -265,7 +354,6 @@ describe("UserStatus", function () {
});

it("Should return 401 for unauthorized request for user and superuser", function (done) {
// Using ONBOARDING state since OOO is now blocked by the validator
chai
.request(app)
.patch(`/users/status/${testUserId}`)
Expand Down
97 changes: 93 additions & 4 deletions test/unit/models/userStatus.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { userFutureStatusData } from "../../fixtures/userFutureStatus/userFutureStatusData";
const { userFutureStatusData } = require("../../fixtures/userFutureStatus/userFutureStatusData");
const chai = require("chai");
const sinon = require("sinon");
const { NotFound, Forbidden } = require("http-errors");
const { expect } = chai;
const firestore = require("../../../utils/firestore");
const userStatusModel = firestore.collection("usersStatus");
const tasksModel = firestore.collection("tasks");
const { cancelOooStatus, addFutureStatus, getUserStatusForUserIds } = require("../../../models/userStatus");
const {
cancelOooStatus,
addFutureStatus,
getUserStatusForUserIds,
updateAllUserStatus,
} = require("../../../models/userStatus");
const cleanDb = require("../../utils/cleanDb");
const addUser = require("../../utils/addUser");
const { userState } = require("../../../constants/userStatus");
const { generateStatusDataForCancelOOO, generateDefaultFutureStatus } = require("../../fixtures/userStatus/userStatus");
const {
generateStatusDataForCancelOOO,
generateDefaultFutureStatus,
generateOooUserStatusDoc,
} = require("../../fixtures/userStatus/userStatus");

describe("tasks", function () {
let userId;
Expand Down Expand Up @@ -38,7 +47,7 @@ describe("tasks", function () {

it("Should clear the future Status if the User cancels OOO", async function () {
const data = generateStatusDataForCancelOOO(userId, userState.OOO);
const from = new Date().getTime() + 24 * 60 * 60 * 1000; // 1 day offset from current time
const from = new Date().getTime() + 24 * 60 * 60 * 1000;
data.futureStatus = generateDefaultFutureStatus(userState.IDLE, from, "");
await docRefUser0.set(data);
const response = await cancelOooStatus(userId);
Expand Down Expand Up @@ -87,6 +96,86 @@ describe("tasks", function () {
expect(response.data.futureStatus.state).to.equal("UPCOMING");
});

describe("updateAllUserStatus", function () {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • you can refactor all tests in this describe block, all of them users same userStatus mock data
  • also please remove comments if aren't necessary

let clock;

beforeEach(async function () {
clock = sinon.useFakeTimers({
now: new Date("2026-07-14T02:00:00.000Z").getTime(),
toFake: ["Date"],
});
});

afterEach(async function () {
clock.restore();
await cleanDb();
});

it("Should update user status when futureStatus.from <= today (e.g. from is in the past)", async function () {
const today = Date.now();
const docRef = userStatusModel.doc();

const userStatusData = generateOooUserStatusDoc(userId, today, {
currentStatusFromOffset: -2 * 24 * 60 * 60 * 1000,
futureStatusFromOffset: -24 * 60 * 60 * 1000,
});
await docRef.set(userStatusData);

const summary = await updateAllUserStatus();
expect(summary.usersCount).to.equal(1);
expect(summary.oooUsersAltered).to.equal(1);

const doc = await docRef.get();
const data = doc.data();

expect(data.currentStatus.state).to.equal(userState.ACTIVE);
expect(data.currentStatus.from).to.equal(today - 24 * 60 * 60 * 1000);
expect(data.futureStatus).to.equal(undefined);
});

it("Should update user status when futureStatus.from === today (boundary case)", async function () {
const today = Date.now();
const docRef = userStatusModel.doc();

const userStatusData = generateOooUserStatusDoc(userId, today, {
currentStatusUntilOffset: 24 * 60 * 60 * 1000,
futureStatusFromOffset: 0,
});
await docRef.set(userStatusData);

const summary = await updateAllUserStatus();
expect(summary.usersCount).to.equal(1);
expect(summary.oooUsersAltered).to.equal(1);

const doc = await docRef.get();
const data = doc.data();

expect(data.currentStatus.state).to.equal(userState.ACTIVE);
expect(data.currentStatus.from).to.equal(today);
expect(data.futureStatus).to.equal(undefined);
});

it("Should not update user status when futureStatus.from > today (e.g. from is in the future)", async function () {
const today = Date.now();
const docRef = userStatusModel.doc();

const userStatusData = generateOooUserStatusDoc(userId, today, {
futureStatusFromOffset: 24 * 60 * 60 * 1000,
});
await docRef.set(userStatusData);

const summary = await updateAllUserStatus();
expect(summary.usersCount).to.equal(0);
expect(summary.oooUsersAltered).to.equal(0);

const doc = await docRef.get();
const data = doc.data();

expect(data.currentStatus.state).to.equal(userState.OOO);
expect(data.futureStatus.state).to.equal(userState.ACTIVE);
});
});

describe("getUserStatusForUserIds", function () {
it("returns statuses keyed by userId for the given ids", async function () {
await userStatusModel.add({ userId: "user-idle-1", currentStatus: { state: userState.IDLE } });
Expand Down
Loading