-
Notifications
You must be signed in to change notification settings - Fork 265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/one time user colors #735
base: develop
Are you sure you want to change the base?
Changes from all commits
2fed9ad
0f505b7
cd2c96d
5bd0f9a
5e4f75a
04c43e7
964dd56
0f65a11
146304a
01c8138
7a75444
54c94ce
f4d60d3
64f56cc
b210076
804c64c
b2c9c3c
b495022
807df54
2ecedb5
4b0df82
cc10b82
b8c7521
231cbef
48f6ef3
f322598
018f829
62fb581
542d504
7e0d00a
a522f0f
c723578
d76b090
c87fe9c
6aaafd7
c2151cd
0852243
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
const userQuery = require("../models/userMigrations"); | ||
const { SOMETHING_WENT_WRONG } = require("../constants/errorMessages"); | ||
|
||
/** | ||
* Returns the lists of usernames where default colors were added | ||
* | ||
* @param req {Object} - Express request object | ||
* @param res {Object} - Express response object | ||
*/ | ||
|
||
const addDefaultColors = async (req, res) => { | ||
try { | ||
const usersDetails = await userQuery.addDefaultColors(); | ||
|
||
return res.json({ | ||
message: "User colors updated successfully!", | ||
usersDetails, | ||
}); | ||
} catch (error) { | ||
logger.error(`Error adding default colors to users: ${error}`); | ||
return res.boom.badImplementation(SOMETHING_WENT_WRONG); | ||
} | ||
}; | ||
|
||
module.exports = { | ||
addDefaultColors, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
const firestore = require("../utils/firestore"); | ||
const userModel = firestore.collection("users"); | ||
const { getRandomIndex } = require("../utils/helper"); | ||
const dataAccess = require("../services/dataAccessLayer"); | ||
const MAX_TRANSACTION_WRITES = 500; | ||
const MAX_USERS_SIZE = 10_000; | ||
const USER_COLORS = 10; | ||
|
||
/** | ||
* Returns the object with details about users to whom user color was added | ||
* | ||
* @param req {Object} - Express request object | ||
* @param res {Object} - Express response object | ||
*/ | ||
|
||
const addDefaultColors = async (batchSize = MAX_TRANSACTION_WRITES) => { | ||
try { | ||
const usersSnapshotArr = await dataAccess.retrieveUsers({ query: { size: MAX_USERS_SIZE } }); | ||
const usersArr = usersSnapshotArr.users; | ||
|
||
const batchArray = []; | ||
const users = []; | ||
batchArray.push(firestore.batch()); | ||
let operationCounter = 0; | ||
let batchIndex = 0; | ||
let totalCount = 0; | ||
|
||
for (const user of usersArr) { | ||
const colors = user.colors ?? {}; | ||
|
||
if (!user.colors) { | ||
const userColorIndex = getRandomIndex(USER_COLORS); | ||
colors.color_id = userColorIndex; | ||
const docId = userModel.doc(user.id); | ||
user.colors = colors; | ||
batchArray[parseInt(batchIndex)].set(docId, user); | ||
operationCounter++; | ||
totalCount++; | ||
users.push(user.username); | ||
if (operationCounter === batchSize) { | ||
batchArray.push(firestore.batch()); | ||
batchIndex++; | ||
operationCounter = 0; | ||
} | ||
} | ||
} | ||
batchArray.forEach(async (batch) => await batch.commit()); | ||
|
||
return { | ||
totalUsersFetched: usersArr.length, | ||
totalUsersUpdated: totalCount, | ||
totalUsersUnaffected: usersArr.length - totalCount, | ||
}; | ||
} catch (err) { | ||
logger.error("Error adding default colors to users", err); | ||
throw err; | ||
} | ||
}; | ||
|
||
module.exports = { | ||
addDefaultColors, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const express = require("express"); | ||
const router = express.Router(); | ||
const authenticate = require("../middlewares/authenticate"); | ||
const authorizeRoles = require("../middlewares/authorizeRoles"); | ||
const { SUPERUSER } = require("../constants/roles"); | ||
const migrations = require("../controllers/userMigrations"); | ||
|
||
router.patch("/user-default-color", authenticate, authorizeRoles([SUPERUSER]), migrations.addDefaultColors); | ||
Check failure Code scanning / CodeQL Missing rate limiting
This route handler performs [authorization](1), but is not rate-limited.
This route handler performs [authorization](2), but is not rate-limited.
This route handler performs [authorization](3), but is not rate-limited.
|
||
|
||
module.exports = router; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
const chai = require("chai"); | ||
const { expect } = chai; | ||
const chaiHttp = require("chai-http"); | ||
|
||
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 nonSuperUser = userData[0]; | ||
const colorBearingUsernames = [superUser.username, nonSuperUser.username]; | ||
isVivek99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const config = require("config"); | ||
const cookieName = config.get("userToken.cookieName"); | ||
|
||
chai.use(chaiHttp); | ||
|
||
describe("userColorMigrations", function () { | ||
let superUserId; | ||
let superUserAuthToken; | ||
let userId = ""; | ||
let nonSuperUserId = ""; | ||
beforeEach(async function () { | ||
userId = await addUser(nonSuperUser); | ||
superUserId = await addUser(superUser); | ||
nonSuperUserId = userId; | ||
superUserAuthToken = authService.generateAuthToken({ userId: superUserId }); | ||
}); | ||
|
||
afterEach(async function () { | ||
await cleanDb(); | ||
}); | ||
|
||
describe("PATCH /migrations/user-default-color", function () { | ||
it("Should return 401 if user is not a super user", function (done) { | ||
const nonSuperUserJwt = authService.generateAuthToken({ userId: nonSuperUserId }); | ||
chai | ||
.request(app) | ||
.patch(`/migrations/user-default-color`) | ||
.set("cookie", `${cookieName}=${nonSuperUserJwt}`) | ||
.end((err, res) => { | ||
if (err) { | ||
return done(err); | ||
} | ||
expect(res).to.have.status(401); | ||
expect(res.body).to.be.a("object"); | ||
expect(res.body.message).to.equal("You are not authorized for this action."); | ||
return done(); | ||
}); | ||
}); | ||
it("Should add default color property to all users,using authorized user (super_user)", function (done) { | ||
chai | ||
.request(app) | ||
.patch(`/migrations/user-default-color`) | ||
.set("cookie", `${cookieName}=${superUserAuthToken}`) | ||
.end((err, res) => { | ||
if (err) { | ||
return done(err); | ||
} | ||
|
||
expect(res).to.have.status(200); | ||
expect(res.body.usersDetails.totalUsersFetched).to.be.equal(colorBearingUsernames.length); | ||
expect(res.body.usersDetails.totalUsersUpdated).to.be.equal(colorBearingUsernames.length); | ||
expect(res.body.usersDetails.totalUsersUnaffected).to.be.equal(0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we also write a test in which we test users who have the color property pre-existing are not affected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure , i had that check, but after removing usernames i removed it, ill add it back in some other. way There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @isVivek99 still can't see the assertion for users who have the color property pre-existing and are not affected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i have added a similar test in the model test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
it would be great if we could add in integration test. This expansion in coverage will enhance our confidence in the system and would assure everything works end to end. 😊 |
||
return done(); | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
const chai = require("chai"); | ||
const { expect } = chai; | ||
const firestore = require("../../../utils/firestore"); | ||
const userModel = firestore.collection("users"); | ||
const cleanDb = require("../../utils/cleanDb"); | ||
const userMigrationModel = require("../../../models/userMigrations"); | ||
const userData = require("../../fixtures/user/user")(); | ||
const addUser = require("../../utils/addUser"); | ||
|
||
describe("userColorMigrations", function () { | ||
const MAX_TRANSACTION_WRITES = 500; | ||
|
||
beforeEach(async function () { | ||
await addUser(userData[0]); | ||
await addUser(userData[1]); | ||
await addUser(userData[2]); | ||
await addUser(userData[3]); | ||
await addUser(userData[4]); | ||
await addUser(userData[6]); | ||
}); | ||
afterEach(async function () { | ||
await cleanDb(); | ||
}); | ||
|
||
it("should add color property to added users which dont have a color property", async function () { | ||
const response = await userMigrationModel.addDefaultColors(); | ||
|
||
expect(response.totalUsersFetched).to.equal(6); | ||
expect(response.totalUsersUpdated).to.equal(5); | ||
expect(response.totalUsersUnaffected).to.equal(1); | ||
}); | ||
it("should make sure that batch updates are working properly by passing smaller batch size", async function () { | ||
const SMALL_BATCH_SIZE = 2; | ||
const response = await userMigrationModel.addDefaultColors(SMALL_BATCH_SIZE); | ||
expect(response.totalUsersFetched).to.equal(6); | ||
expect(response.totalUsersUpdated).to.equal(5); | ||
expect(response.totalUsersUnaffected).to.equal(1); | ||
}); | ||
it("should not affect users already having color property", async function () { | ||
// Manually add a color property to a user | ||
const userId = await addUser(userData[0]); | ||
await userModel.doc(userId).update({ colors: { color_id: 3 } }); | ||
const response = await userMigrationModel.addDefaultColors(MAX_TRANSACTION_WRITES); | ||
expect(response.totalUsersFetched).to.equal(6); | ||
expect(response.totalUsersUpdated).to.equal(4); | ||
expect(response.totalUsersUnaffected).to.equal(2); | ||
heyrandhir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Check that the user with a color property was unaffected | ||
const updatedUser = await userModel.doc(userId).get(); | ||
expect(updatedUser.data().colors.color_id).to.equal(3); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const chai = require("chai"); | ||
const { getRandomIndex } = require("../../../utils/helper"); | ||
const { expect } = chai; | ||
|
||
describe("helpers", function () { | ||
describe("getRandom Index from function", function () { | ||
it("should return a random number between 0 and 10 excluding 10 if no index is passed", function () { | ||
const result = getRandomIndex(); | ||
expect(result).to.be.at.least(0); | ||
expect(result).to.be.below(10); | ||
}); | ||
|
||
it("expect a number between 0 and passed number", function () { | ||
const delimiter = 100; | ||
const result = getRandomIndex(delimiter); | ||
expect(result).to.be.at.least(0); | ||
expect(result).to.be.below(delimiter); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you are aware @ankushdharkar has strongly advised against approving pull requests that don't adhere to the correct naming convention. As
migrations
isn't a resource, could you kindly consider relocating this route under\users
?https://discord.com/channels/673083527624916993/729399523268624405/1141334143867822081