Skip to content

Conversation

@AnujChhikara
Copy link
Contributor

@AnujChhikara AnujChhikara commented Feb 2, 2026

Date: 3 Feb, 2026

Developer Name: @AnujChhikara


Issue Ticket Number

Description

  • Added new endpoint for the image upload for the applicants

Documentation Updated?

  • Yes
  • No

Under Feature Flag

  • Yes
  • No

Database Changes

  • Yes
  • No

Breaking Changes

  • Yes
  • No

Development Tested?

  • Yes
  • No

Screenshots

Screenshot 1
Screen.Recording.2026-02-03.at.1.50.48.AM.mov

Test Coverage

Screenshot 1

Additional Notes

- Added integration tests for the new image upload endpoint `/applications/picture`, covering authentication and file validation scenarios.
- Enhanced unit tests for the applications controller to include image upload functionality.
- Implemented middleware validation for image files, ensuring only valid formats (PNG, JPEG) are accepted and handling errors appropriately.
@AnujChhikara AnujChhikara self-assigned this Feb 2, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

Summary by CodeRabbit

  • New Features
    • Added profile picture upload functionality for users
    • Implemented image validation supporting PNG and JPEG formats
    • Enhanced error messaging for missing or invalid image uploads

Walkthrough

A new application picture upload feature is added, including error message constants, Cloudinary configuration, a POST endpoint with file validation middleware, image service integration for Cloudinary uploads, and comprehensive test coverage for both success and failure scenarios.

Changes

Cohort / File(s) Summary
Constants
constants/application.ts, constants/cloudinary.ts
Added error messages for picture file validation (PICTURE_FILE_MISSING, PICTURE_FILE_EMPTY) and Cloudinary configuration for application folder and tags.
Picture Upload Implementation
middlewares/validators/application.ts, controllers/applications.ts, routes/applications.ts, services/imageService.js
New middleware validator for picture uploads with file presence, buffer, and MIME type checks; controller endpoint for handling uploads; route definition with Multer integration; service function to encode and upload images to Cloudinary.
Test Coverage
test/integration/application.test.ts, test/unit/controllers/applications.test.ts, test/unit/middlewares/application-validator.test.ts
Integration tests for authenticated/unauthenticated upload scenarios and unit tests for controller and middleware validation with success and error path coverage.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Router
    participant Authenticate as Auth Middleware
    participant ValidatePic as Validator Middleware
    participant Controller
    participant ImageService
    participant Cloudinary

    Client->>Router: POST /applications/picture (file, auth token)
    Router->>Authenticate: Check authentication
    Authenticate->>Router: User verified
    Router->>ValidatePic: Validate file
    ValidatePic->>ValidatePic: Check file presence & MIME type
    ValidatePic->>Router: Validation passed
    Router->>Controller: Forward request
    Controller->>ImageService: uploadApplicationImage({file, userId})
    ImageService->>ImageService: Encode to Base64 Data URI
    ImageService->>Cloudinary: Upload with public_id
    Cloudinary-->>ImageService: Return publicId & URL
    ImageService-->>Controller: Return {publicId, url}
    Controller-->>Client: 201 Created {message, image}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • iamitprakash

Poem

🐰 A file hops into the burrow with care,
Validated thrice through the validator's snare,
To Cloudinary's clouds it takes flight,
201 success, the picture shines bright!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding image upload functionality for applicants, which matches the primary objective of the pull request across all modified files.
Description check ✅ Passed The description is related to the changeset, providing context about the new applicant image upload endpoint and referencing issue #2565, though detail is minimal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch anuj/application-user-image-upload

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

);
router.post(
"/picture",
authenticate,

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Copilot Autofix

AI 1 day ago

In general, to fix missing rate limiting on sensitive or expensive endpoints, we should add a rate‑limiting middleware that restricts how many requests a client can make within a certain time window. In an Express app, this is commonly done with the express-rate-limit package, which can be applied globally (app.use(limiter)) or per‑route (router.post('/path', limiter, handler)).

For this specific file, the best minimal change is to introduce a rate limiter instance (using express-rate-limit) and apply it to the /picture POST route, which is performing authentication and file upload and is the one directly reported by CodeQL. To avoid altering existing behavior beyond rate limiting, we will insert the limiter middleware into the chain for that route, after authenticate and before upload.single("profile"). This ordering ensures that: (1) only authenticated users are counted for this limiter (if identification is based on user), and (2) rate limiting happens before the actual upload processing, so we avoid wasting resources on rejected requests.

Concretely:

  • At the top of routes/applications.ts, add const rateLimit = require("express-rate-limit");.
  • Define a limiter, e.g. const pictureUploadLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); (or a stricter limit if desired) near where the router is created.
  • Update the /picture route declaration to include pictureUploadLimiter in the middleware list. No other routes need to be changed to satisfy the specific alert, though they could be similarly protected if desired.

Suggested changeset 2
routes/applications.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/applications.ts b/routes/applications.ts
--- a/routes/applications.ts
+++ b/routes/applications.ts
@@ -6,9 +6,15 @@
 const { authorizeOwnOrSuperUser } = require("../middlewares/authorizeOwnOrSuperUser");
 const applicationValidator = require("../middlewares/validators/application");
 const { upload } = require("../utils/multer");
+const rateLimit = require("express-rate-limit");
 
 const router = express.Router();
 
+const pictureUploadLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 100, // limit each IP to 100 picture upload requests per windowMs
+});
+
 router.get(
   "/",
   authenticate,
@@ -19,6 +22,7 @@
 router.post(
   "/picture",
   authenticate,
+  pictureUploadLimiter,
   upload.single("profile"),
   applicationValidator.validateApplicationPicture,
   applications.postUserPicture
EOF
@@ -6,9 +6,15 @@
const { authorizeOwnOrSuperUser } = require("../middlewares/authorizeOwnOrSuperUser");
const applicationValidator = require("../middlewares/validators/application");
const { upload } = require("../utils/multer");
const rateLimit = require("express-rate-limit");

const router = express.Router();

const pictureUploadLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 picture upload requests per windowMs
});

router.get(
"/",
authenticate,
@@ -19,6 +22,7 @@
router.post(
"/picture",
authenticate,
pictureUploadLimiter,
upload.single("profile"),
applicationValidator.validateApplicationPicture,
applications.postUserPicture
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -42,7 +42,8 @@
     "passport-github2": "0.1.12",
     "passport-google-oauth20": "^2.0.0",
     "rate-limiter-flexible": "5.0.3",
-    "winston": "3.13.0"
+    "winston": "3.13.0",
+    "express-rate-limit": "^8.2.1"
   },
   "devDependencies": {
     "@types/chai": "4.3.16",
EOF
@@ -42,7 +42,8 @@
"passport-github2": "0.1.12",
"passport-google-oauth20": "^2.0.0",
"rate-limiter-flexible": "5.0.3",
"winston": "3.13.0"
"winston": "3.13.0",
"express-rate-limit": "^8.2.1"
},
"devDependencies": {
"@types/chai": "4.3.16",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.2.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
@MayankBansal12 MayankBansal12 marked this pull request as ready for review February 4, 2026 05:50
@MayankBansal12
Copy link
Member

@coderabbitai review bro

@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

@MayankBansal12: Sure thing! I'll review the changes in this PR right away. 🚀

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@routes/applications.ts`:
- Around line 19-25: The POST /picture route lacks rate limiting—add the
existing commonRateLimiter middleware into the router.post middleware chain
before the upload handler so requests hit rate limits; specifically update the
router.post call that currently lists authenticate, upload.single("profile"),
applicationValidator.validateApplicationPicture, and
applications.postUserPicture to include commonRateLimiter (imported from
middlewares/rateLimiting.js) in the middleware sequence (e.g., after
authenticate and before upload.single) so the route uses commonRateLimiter and
still calls applicationValidator.validateApplicationPicture and
applications.postUserPicture.

In `@test/integration/application.test.ts`:
- Around line 361-397: Add integration tests for the validation/error paths
around the POST /applications/picture route: add a test that sends no attachment
and asserts the response is the expected validation error from
validateApplicationPicture (e.g., 400 and appropriate error message), a test
that attaches an invalid file type (e.g., .txt or .gif) and asserts the
middleware returns the correct validation response, and a test where
sinon.stub(imageService, "uploadApplicationImage") is made to reject to assert
the route returns the service error (status/code and message) and is handled
correctly; place these alongside the existing tests in application.test.ts and
reuse the same auth cookie setup (cookieName/secondUserJwt) and
mockUploadResponse reference but adjust stubbing/attachment to trigger each
path.
- Around line 381-396: The test stubs imageService.uploadApplicationImage but
never restores it, risking test pollution; update the test suite to restore the
stub after the case by adding an afterEach that calls sinon.restore() (or
explicitly calls uploadApplicationImage.restore() if you capture the stub
variable) so the stub created in the "should return 201 and image data when
authenticated with valid image" test is removed before other tests run; locate
the stub usage in the test that posts to "/applications/picture" and add the
afterEach cleanup alongside other per-test cleanup blocks.

Comment on lines +361 to +397
describe("POST /applications/picture", function () {
const mockUploadResponse = {
publicId: "applications/test-user/profile",
url: "https://res.cloudinary.com/example/applications/test-user/profile.png",
};

it("should return 401 when not authenticated", function (done) {
chai
.request(app)
.post("/applications/picture")
.type("form")
.attach("profile", Buffer.from("fake-image-data"), "test.png")
.end((err, res) => {
if (err) return done(err);
expect(res).to.have.status(401);
expect(res.body.error).to.equal("Unauthorized");
return done();
});
});

it("should return 201 and image data when authenticated with valid image", function (done) {
sinon.stub(imageService, "uploadApplicationImage").resolves(mockUploadResponse);
chai
.request(app)
.post("/applications/picture")
.set("cookie", `${cookieName}=${secondUserJwt}`)
.type("form")
.attach("profile", Buffer.from("fake-image-data"), "test.png")
.end((err, res) => {
if (err) return done(err);
expect(res).to.have.status(201);
expect(res.body.message).to.equal("Image uploaded successfully");
expect(res.body.image).to.deep.equal(mockUploadResponse);
return done();
});
});
});
Copy link

@coderabbitai coderabbitai bot Feb 4, 2026

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding tests for validation error paths.

The test suite covers authentication and the happy path, but lacks integration tests for validation scenarios handled by validateApplicationPicture middleware:

  • Missing file (no attachment)
  • Invalid file type (e.g., .txt or .gif)
  • Service failure (when uploadApplicationImage rejects)

These would ensure the middleware and error handling work correctly end-to-end.

🤖 Prompt for AI Agents
In `@test/integration/application.test.ts` around lines 361 - 397, Add integration
tests for the validation/error paths around the POST /applications/picture
route: add a test that sends no attachment and asserts the response is the
expected validation error from validateApplicationPicture (e.g., 400 and
appropriate error message), a test that attaches an invalid file type (e.g.,
.txt or .gif) and asserts the middleware returns the correct validation
response, and a test where sinon.stub(imageService, "uploadApplicationImage") is
made to reject to assert the route returns the service error (status/code and
message) and is handled correctly; place these alongside the existing tests in
application.test.ts and reuse the same auth cookie setup
(cookieName/secondUserJwt) and mockUploadResponse reference but adjust
stubbing/attachment to trigger each path.

Copy link
Member

Choose a reason for hiding this comment

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

can you show me the code for working test for this point

Copy link

Choose a reason for hiding this comment

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

@MayankBansal12, here are the three working test cases for the validation error paths:

describe("POST /applications/picture", function () {
  const mockUploadResponse = {
    publicId: "applications/test-user/profile",
    url: "https://res.cloudinary.com/example/applications/test-user/profile.png",
  };

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

  it("should return 401 when not authenticated", function (done) {
    chai
      .request(app)
      .post("/applications/picture")
      .type("form")
      .attach("profile", Buffer.from("fake-image-data"), "test.png")
      .end((err, res) => {
        if (err) return done(err);
        expect(res).to.have.status(401);
        expect(res.body.error).to.equal("Unauthorized");
        return done();
      });
  });

  it("should return 201 and image data when authenticated with valid image", function (done) {
    sinon.stub(imageService, "uploadApplicationImage").resolves(mockUploadResponse);
    chai
      .request(app)
      .post("/applications/picture")
      .set("cookie", `${cookieName}=${secondUserJwt}`)
      .type("form")
      .attach("profile", Buffer.from("fake-image-data"), "test.png")
      .end((err, res) => {
        if (err) return done(err);
        expect(res).to.have.status(201);
        expect(res.body.message).to.equal("Image uploaded successfully");
        expect(res.body.image).to.deep.equal(mockUploadResponse);
        return done();
      });
  });

  it("should return 400 when no file is attached", function (done) {
    chai
      .request(app)
      .post("/applications/picture")
      .set("cookie", `${cookieName}=${secondUserJwt}`)
      .type("form")
      .end((err, res) => {
        if (err) return done(err);
        expect(res).to.have.status(400);
        expect(res.body.error).to.equal("Bad Request");
        expect(res.body.message).to.equal(APPLICATION_ERROR_MESSAGES.PICTURE_FILE_MISSING);
        return done();
      });
  });

  it("should return 400 when file type is not supported", function (done) {
    chai
      .request(app)
      .post("/applications/picture")
      .set("cookie", `${cookieName}=${secondUserJwt}`)
      .type("form")
      .attach("profile", Buffer.from("fake-text-data"), "test.txt")
      .end((err, res) => {
        if (err) return done(err);
        expect(res).to.have.status(400);
        expect(res.body.error).to.equal("Bad Request");
        expect(res.body.message).to.include("Only image/png and image/jpeg formats are supported");
        return done();
      });
  });

  it("should return 500 when image upload service fails", function (done) {
    sinon.stub(imageService, "uploadApplicationImage").rejects(new Error("Cloudinary service error"));
    chai
      .request(app)
      .post("/applications/picture")
      .set("cookie", `${cookieName}=${secondUserJwt}`)
      .type("form")
      .attach("profile", Buffer.from("fake-image-data"), "test.png")
      .end((err, res) => {
        if (err) return done(err);
        expect(res).to.have.status(500);
        expect(res.body.error).to.equal("Internal Server Error");
        return done();
      });
  });
});

Key additions:

  1. Missing file test - Simply omits the .attach() call to trigger the PICTURE_FILE_MISSING validation error
  2. Invalid file type test - Attaches a .txt file to trigger the MIME type validation error
  3. Service failure test - Stubs uploadApplicationImage to reject with an error, expecting a 500 response
  4. Added afterEach - Restores all sinon stubs after each test to prevent side effects between tests

These tests cover the middleware validation and error handling end-to-end as requested.

Comment on lines +381 to +396
it("should return 201 and image data when authenticated with valid image", function (done) {
sinon.stub(imageService, "uploadApplicationImage").resolves(mockUploadResponse);
chai
.request(app)
.post("/applications/picture")
.set("cookie", `${cookieName}=${secondUserJwt}`)
.type("form")
.attach("profile", Buffer.from("fake-image-data"), "test.png")
.end((err, res) => {
if (err) return done(err);
expect(res).to.have.status(201);
expect(res.body.message).to.equal("Image uploaded successfully");
expect(res.body.image).to.deep.equal(mockUploadResponse);
return done();
});
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stub not restored after test, risking test pollution.

The sinon.stub(imageService, "uploadApplicationImage") created on line 382 is never restored. While there's a global sinon.restore() in the after hook, subsequent tests within this file could be affected if they rely on the real imageService. Other test blocks like "PATCH /applications/:applicationId/nudge" properly use afterEach for cleanup.

🧹 Proposed fix: Add afterEach for cleanup
   describe("POST /applications/picture", function () {
     const mockUploadResponse = {
       publicId: "applications/test-user/profile",
       url: "https://res.cloudinary.com/example/applications/test-user/profile.png",
     };
+
+    afterEach(function () {
+      sinon.restore();
+    });

     it("should return 401 when not authenticated", function (done) {
🤖 Prompt for AI Agents
In `@test/integration/application.test.ts` around lines 381 - 396, The test stubs
imageService.uploadApplicationImage but never restores it, risking test
pollution; update the test suite to restore the stub after the case by adding an
afterEach that calls sinon.restore() (or explicitly calls
uploadApplicationImage.restore() if you capture the stub variable) so the stub
created in the "should return 201 and image data when authenticated with valid
image" test is removed before other tests run; locate the stub usage in the test
that posts to "/applications/picture" and add the afterEach cleanup alongside
other per-test cleanup blocks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants