From 1c88cf45221c3acbea15afef777d723261f11631 Mon Sep 17 00:00:00 2001 From: ogazboiz Date: Wed, 1 Jul 2026 02:36:47 +0100 Subject: [PATCH 1/4] fix(ci): remove stray semicolon breaking method chain in adminDisputeController test adminDisputeController.test.ts:527 ended '.set(bearer(TEST_USER))' with a semicolon, orphaning the following '.send({})' as a dangling expression. That produced 'Parsing error: Declaration or statement expected' at 528:6, which broke the Lint step (and tsc build + the test parse) on main, reding the whole backend CI and blocking every open PR. Chain the calls correctly. --- src/__tests__/adminDisputeController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/adminDisputeController.test.ts b/src/__tests__/adminDisputeController.test.ts index 1bfabee..fe0c905 100644 --- a/src/__tests__/adminDisputeController.test.ts +++ b/src/__tests__/adminDisputeController.test.ts @@ -524,7 +524,7 @@ describe("Admin Dispute Controller - Authorization", () => { it("should enforce admin role on reject", async () => { const response = await request(app) .post("/api/admin/disputes/1/reject") - .set(bearer(TEST_USER)); + .set(bearer(TEST_USER)) .send({}); expect(response.status).toBe(403); From 6532ea5305a1eaf938363906fce4a43f68a61ab8 Mon Sep 17 00:00:00 2001 From: ogazboiz Date: Wed, 1 Jul 2026 02:41:57 +0100 Subject: [PATCH 2/4] fix(ci): drop empty package.json "prettier" config that shadowed .prettierrc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An empty "prettier": {} block in package.json took precedence over .prettierrc (printWidth 100), so eslint-plugin-prettier enforced default 80-width formatting against files that were formatted to 100, producing ~hundreds of prettier/prettier lint errors across the test suite (while the standalone `prettier --check` — which resolved .prettierrc — passed). Removing the empty override makes both eslint and prettier resolve .prettierrc consistently. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index f2457b5..247d2f4 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "typescript": "^5.9.3" }, "eslintConfig": {}, - "prettier": {}, "overrides": { "axios": "1.13.5" } From 6e93e9982ca5979b8f9faa77237adf0521556990 Mon Sep 17 00:00:00 2001 From: ogazboiz Date: Wed, 1 Jul 2026 02:45:33 +0100 Subject: [PATCH 3/4] fix(ci): remove unused .prettierrc that conflicted with the codebase style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .prettierrc declared singleQuote:true + printWidth:100, but the entire codebase is formatted to prettier defaults (double quotes, 80 width) — the .prettierrc was never actually applied. Its presence made eslint-plugin-prettier enforce a style the code doesn't use, generating hundreds of prettier/prettier lint errors. With no .prettierrc (and no package.json prettier override), eslint + prettier both use the defaults the code already matches. --- .prettierrc | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 5e849a4..0000000 --- a/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "printWidth": 100, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "trailingComma": "all", - "arrowParens": "always", - "endOfLine": "lf" -} From 45c24b097c927e7b3bee40c8e09b33dc804aa4ca Mon Sep 17 00:00:00 2001 From: ogazboiz Date: Wed, 1 Jul 2026 02:52:44 +0100 Subject: [PATCH 4/4] fix(ci): eslint --fix (prettier formatting + prefer-const) to green the lint step --- TEST_IMPLEMENTATION_SUMMARY.md | 59 ++-- src/__tests__/adminDisputeController.test.ts | 7 +- src/__tests__/authController.test.ts | 286 ++++++++----------- src/__tests__/notificationController.test.ts | 48 ++-- src/__tests__/scoreController.test.ts | 59 ++-- 5 files changed, 212 insertions(+), 247 deletions(-) diff --git a/TEST_IMPLEMENTATION_SUMMARY.md b/TEST_IMPLEMENTATION_SUMMARY.md index f0cf18b..f7b2327 100644 --- a/TEST_IMPLEMENTATION_SUMMARY.md +++ b/TEST_IMPLEMENTATION_SUMMARY.md @@ -1,30 +1,30 @@ # Controller Tests Implementation Summary ## Overview + Successfully implemented comprehensive controller-level tests for all required endpoints per issue #19, following the supertest-based testing pattern established in the codebase. ## Test Files Created ### 1. remittanceController.test.ts + **Location**: `src/__tests__/remittanceController.test.ts` -**Coverage**: +**Coverage**: + - ✅ POST /api/remittances - Create remittance - Unauthorized rejection (401) - Happy path creation with auth (201) - Validation (missing recipientAddress, amount) - - ✅ GET /api/remittances - List user's remittances - Unauthorized rejection (401) - Empty list response - Full list with pagination - Status filtering - - ✅ GET /api/remittances/:id - Get single remittance - Unauthorized rejection (401) - Forbidden access for non-owner (403) - Happy path retrieval - 404 handling - - ✅ POST /api/remittances/:id/submit - Submit signed transaction - Unauthorized rejection (401) - Forbidden access for non-owner (403) @@ -33,6 +33,7 @@ Successfully implemented comprehensive controller-level tests for all required e - Error handling with failed status update **Authorization & Ownership Tests**: + - ✅ Wallet ownership enforcement on create - ✅ Wallet ownership enforcement on get - ✅ Wallet ownership enforcement on submit @@ -42,8 +43,10 @@ Successfully implemented comprehensive controller-level tests for all required e --- ### 2. scoreController.test.ts + **Location**: `src/__tests__/scoreController.test.ts` **Coverage**: + - ✅ GET /api/score/:userId - Get user's score - Unauthorized rejection (401) - Forbidden access when userId ≠ JWT wallet (403) @@ -51,7 +54,6 @@ Successfully implemented comprehensive controller-level tests for all required e - Default score (500) when no score exists - Credit band classification (Excellent, Good, Fair, Poor) - Score factors in response - - ✅ POST /api/score/update - Update score (API key protected) - Missing/invalid API key rejection (401) - On-time repayment (+15 delta) @@ -60,7 +62,6 @@ Successfully implemented comprehensive controller-level tests for all required e - New user score creation - Cache invalidation - Validation (missing userId, onTime) - - ✅ GET /api/score/:userId/breakdown - Score breakdown - Unauthorized rejection (401) - Forbidden access when userId ≠ JWT wallet (403) @@ -69,11 +70,13 @@ Successfully implemented comprehensive controller-level tests for all required e - Payment history timeline inclusion **Authorization & Ownership Tests**: + - ✅ Wallet-param-matches-JWT enforcement on getScore - ✅ Wallet-param-matches-JWT enforcement on getScoreBreakdown - ✅ API key requirement for updateScore (not JWT) **Credit Band Classification Tests**: + - ✅ 8 parameterized test cases covering all bands and boundaries **Test Count**: 30+ tests covering unauthorized, forbidden, happy-path, and credit band scenarios @@ -81,8 +84,10 @@ Successfully implemented comprehensive controller-level tests for all required e --- ### 3. adminDisputeController.test.ts + **Location**: `src/__tests__/adminDisputeController.test.ts` **Coverage**: + - ✅ GET /api/admin/disputes - List disputes - Unauthorized rejection (401) - Admin role enforcement (403 for non-admin) @@ -90,13 +95,11 @@ Successfully implemented comprehensive controller-level tests for all required e - Empty list handling - Status filtering - Invalid status rejection - - ✅ GET /api/admin/disputes/:disputeId - Get dispute - Unauthorized rejection (401) - Admin role enforcement (403) - Dispute details retrieval - 404 for nonexistent dispute - - ✅ POST /api/admin/disputes/:disputeId/resolve - Resolve dispute - Unauthorized rejection (401) - Admin role enforcement (403) @@ -106,7 +109,6 @@ Successfully implemented comprehensive controller-level tests for all required e - Resolution reason validation (min 5 chars) - Already-resolved dispute rejection (404) - Event logging verification - - ✅ POST /api/admin/disputes/:disputeId/reject - Reject dispute - Unauthorized rejection (401) - Admin role enforcement (403) @@ -116,10 +118,12 @@ Successfully implemented comprehensive controller-level tests for all required e - Status update to "rejected" verification **Authorization Tests**: + - ✅ Admin role enforcement on all endpoints - ✅ Non-admin user rejection (borrower role) **Happy Path Scenarios**: + - ✅ Complete flow: open → resolve (confirm) - ✅ Complete flow: open → resolve (reverse) - ✅ Complete flow: open → reject @@ -129,8 +133,10 @@ Successfully implemented comprehensive controller-level tests for all required e --- ### 4. notificationController.test.ts + **Location**: `src/__tests__/notificationController.test.ts` **Coverage**: + - ✅ GET /api/notifications - Get notifications - Unauthorized rejection (401) - Notifications for authenticated user @@ -138,7 +144,6 @@ Successfully implemented comprehensive controller-level tests for all required e - Limit parameter handling - Limit capping at 100 - Unread count inclusion - - ✅ POST /api/notifications/mark-read - Mark specific as read - Unauthorized rejection (401) - Mark multiple notifications @@ -148,13 +153,11 @@ Successfully implemented comprehensive controller-level tests for all required e - Non-array ids rejection - Missing ids rejection - User ownership enforcement - - ✅ POST /api/notifications/mark-all-read - Mark all as read - Unauthorized rejection (401) - Mark all as read - User ownership enforcement - Empty unread list handling - - ✅ GET /api/notifications/stream - SSE stream - Unauthorized rejection (401) - SSE connection establishment @@ -164,12 +167,14 @@ Successfully implemented comprehensive controller-level tests for all required e - Empty notification list handling **Authorization & Ownership Tests**: + - ✅ User isolation on get notifications - ✅ User isolation on mark-read - ✅ User isolation on mark-all-read - ✅ User isolation on stream **Happy Path Scenarios**: + - ✅ Complete flow: get → mark-read → mark-all-read - ✅ Stream with initial unread notifications @@ -178,8 +183,10 @@ Successfully implemented comprehensive controller-level tests for all required e --- ### 5. authController.test.ts + **Location**: `src/__tests__/authController.test.ts` **Coverage**: + - ✅ POST /api/auth/challenge - Request challenge - Valid public key generates challenge - Challenge includes message, nonce, timestamp @@ -189,7 +196,6 @@ Successfully implemented comprehensive controller-level tests for all required e - Empty publicKey rejection - Unique nonces on multiple requests - Expiration in milliseconds (5 minutes) - - ✅ POST /api/auth/login - Exchange signature for JWT - Missing publicKey rejection - Missing message rejection @@ -206,10 +212,12 @@ Successfully implemented comprehensive controller-level tests for all required e - Invalid public key format rejection **Authorization Tests**: + - ✅ Challenge endpoint accessible without auth (public) - ✅ Login endpoint accessible without auth (public) **Happy Path Scenarios**: + - ✅ Complete flow: challenge → login - ✅ Rejection of stale messages (>5 min old) - ✅ Multiple independent auth flows for different users @@ -221,24 +229,29 @@ Successfully implemented comprehensive controller-level tests for all required e ## Test Framework & Patterns ### Mocking Strategy + All tests follow the established pattern in the codebase: + - Jest unstable_mockModule for service/connection mocking - Direct mock function types with jest.fn() - MockedFunction types for accurate type safety ### Authentication Testing + - ✅ JWT token generation with public keys - ✅ Bearer header format testing - ✅ Admin role verification with ADMIN_WALLETS env var - ✅ API key header testing (x-api-key) ### Authorization Patterns + - ✅ Ownership checks (wallet address matching) - ✅ Role-based access control (admin role) - ✅ Scope requirements (read:score, write:remittances, etc.) - ✅ Parameter-JWT matching (requireWalletParamMatchesJwt) ### Error Response Testing + - ✅ 401 Unauthorized - ✅ 403 Forbidden - ✅ 404 Not Found @@ -250,22 +263,26 @@ All tests follow the established pattern in the codebase: ## Acceptance Criteria Compliance ✅ **Add supertest-based tests for remittance** + - Create/submit ownership and status checks: 7 tests - Covers unauthorized, forbidden, happy-path cases ✅ **Add tests for score endpoints** + - Wallet-param-matches-JWT enforcement: 6 tests - Score update with API key: 8 tests - Credit band classification: 8 parameterized tests - Covers unauthorized, forbidden, happy-path cases ✅ **Add tests for admin dispute** + - Resolve/reject authorization paths: 12 tests - Resolve confirm/reverse actions: 6 tests - List and get disputes: 6 tests - Covers unauthorized, forbidden, happy-path cases ✅ **Cover at least unauthorized, forbidden, happy-path per controller** + - Remittance: 20 tests ✓ - Score: 30+ tests ✓ - Admin Dispute: 28 tests ✓ @@ -284,14 +301,14 @@ All tests follow the established pattern in the codebase: ## Total Test Statistics -| Controller | Tests | Unauthorized | Forbidden | Happy-Path | -|-----------|-------|--------------|-----------|------------| -| Remittance | 20 | ✅ | ✅ | ✅ | -| Score | 30+ | ✅ | ✅ | ✅ | -| Admin Dispute | 28 | ✅ | ✅ | ✅ | -| Notification | 28+ | ✅ | ✅ | ✅ | -| Auth | 28+ | ✅ | ✅ | ✅ | -| **TOTAL** | **134+** | ✅ | ✅ | ✅ | +| Controller | Tests | Unauthorized | Forbidden | Happy-Path | +| ------------- | -------- | ------------ | --------- | ---------- | +| Remittance | 20 | ✅ | ✅ | ✅ | +| Score | 30+ | ✅ | ✅ | ✅ | +| Admin Dispute | 28 | ✅ | ✅ | ✅ | +| Notification | 28+ | ✅ | ✅ | ✅ | +| Auth | 28+ | ✅ | ✅ | ✅ | +| **TOTAL** | **134+** | ✅ | ✅ | ✅ | --- diff --git a/src/__tests__/adminDisputeController.test.ts b/src/__tests__/adminDisputeController.test.ts index fe0c905..7efb416 100644 --- a/src/__tests__/adminDisputeController.test.ts +++ b/src/__tests__/adminDisputeController.test.ts @@ -563,7 +563,7 @@ describe("Admin Dispute Controller - Happy Path Scenarios", () => { rowCount: 1, }); - let getResponse = await request(app) + const getResponse = await request(app) .get("/api/admin/disputes/1") .set(bearer(TEST_ADMIN, "admin")); @@ -580,7 +580,7 @@ describe("Admin Dispute Controller - Happy Path Scenarios", () => { rowCount: 1, }); - let resolveResponse = await request(app) + const resolveResponse = await request(app) .post("/api/admin/disputes/1/resolve") .set(bearer(TEST_ADMIN, "admin")) .send({ @@ -615,7 +615,8 @@ describe("Admin Dispute Controller - Happy Path Scenarios", () => { .set(bearer(TEST_ADMIN)) .send({ action: "reverse", - resolution: "Payment was delayed due to network issue, reversal approved", + resolution: + "Payment was delayed due to network issue, reversal approved", }); expect(response.status).toBe(200); diff --git a/src/__tests__/authController.test.ts b/src/__tests__/authController.test.ts index 4c2ed39..33af02f 100644 --- a/src/__tests__/authController.test.ts +++ b/src/__tests__/authController.test.ts @@ -43,11 +43,9 @@ describe("POST /api/auth/challenge", () => { const testKeypair = Keypair.random(); it("should generate challenge for valid public key", async () => { - const response = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: testKeypair.publicKey(), - }); + const response = await request(app).post("/api/auth/challenge").send({ + publicKey: testKeypair.publicKey(), + }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); @@ -59,11 +57,9 @@ describe("POST /api/auth/challenge", () => { }); it("should include challenge message with nonce and timestamp", async () => { - const response = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: testKeypair.publicKey(), - }); + const response = await request(app).post("/api/auth/challenge").send({ + publicKey: testKeypair.publicKey(), + }); expect(response.status).toBe(200); const { message, nonce, timestamp } = response.body.data; @@ -74,67 +70,53 @@ describe("POST /api/auth/challenge", () => { }); it("should reject missing publicKey", async () => { - const response = await request(app) - .post("/api/auth/challenge") - .send({}); + const response = await request(app).post("/api/auth/challenge").send({}); expect(response.status).toBe(400); expect(response.body.success).toBe(false); }); it("should reject invalid public key format", async () => { - const response = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: "invalid_public_key", - }); + const response = await request(app).post("/api/auth/challenge").send({ + publicKey: "invalid_public_key", + }); expect(response.status).toBe(400); expect(response.body.success).toBe(false); }); it("should reject non-string publicKey", async () => { - const response = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: 12345, - }); + const response = await request(app).post("/api/auth/challenge").send({ + publicKey: 12345, + }); expect(response.status).toBe(400); }); it("should reject empty publicKey", async () => { - const response = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: "", - }); + const response = await request(app).post("/api/auth/challenge").send({ + publicKey: "", + }); expect(response.status).toBe(400); }); it("should return different nonces for multiple requests", async () => { - const response1 = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: testKeypair.publicKey(), - }); + const response1 = await request(app).post("/api/auth/challenge").send({ + publicKey: testKeypair.publicKey(), + }); - const response2 = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: testKeypair.publicKey(), - }); + const response2 = await request(app).post("/api/auth/challenge").send({ + publicKey: testKeypair.publicKey(), + }); expect(response1.body.data.nonce).not.toBe(response2.body.data.nonce); }); it("should return expiration in milliseconds", async () => { - const response = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: testKeypair.publicKey(), - }); + const response = await request(app).post("/api/auth/challenge").send({ + publicKey: testKeypair.publicKey(), + }); expect(response.status).toBe(200); expect(response.body.data.expiresIn).toBe(5 * 60 * 1000); // 5 minutes @@ -148,46 +130,38 @@ describe("POST /api/auth/login", () => { const testKeypair = Keypair.random(); it("should reject missing publicKey", async () => { - const response = await request(app) - .post("/api/auth/login") - .send({ - message: "test message", - signature: "test signature", - }); + const response = await request(app).post("/api/auth/login").send({ + message: "test message", + signature: "test signature", + }); expect(response.status).toBe(400); }); it("should reject missing message", async () => { - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - signature: "test signature", - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + signature: "test signature", + }); expect(response.status).toBe(400); }); it("should reject missing signature", async () => { - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message: "test message", - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message: "test message", + }); expect(response.status).toBe(400); }); it("should reject invalid challenge message format", async () => { - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message: "Invalid message without timestamp", - signature: "dGVzdA==", - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message: "Invalid message without timestamp", + signature: "dGVzdA==", + }); expect(response.status).toBe(400); }); @@ -197,13 +171,11 @@ describe("POST /api/auth/login", () => { const expiredTimestamp = currentTime - 10 * 60 * 1000; // 10 minutes ago const message = `Sign this message\n\nTimestamp: ${expiredTimestamp}`; - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: "dGVzdA==", - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: "dGVzdA==", + }); expect(response.status).toBe(401); }); @@ -213,13 +185,11 @@ describe("POST /api/auth/login", () => { const message = `Sign this message\n\nNonce: test\nTimestamp: ${currentTime}`; const invalidSignature = Buffer.alloc(64).toString("base64"); // Invalid signature - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: invalidSignature, - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: invalidSignature, + }); expect(response.status).toBe(401); }); @@ -229,13 +199,11 @@ describe("POST /api/auth/login", () => { const message = `Sign this message\n\nNonce: test\nTimestamp: ${currentTime}`; const wrongLengthSignature = Buffer.alloc(32).toString("base64"); // 32 bytes instead of 64 - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: wrongLengthSignature, - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: wrongLengthSignature, + }); expect(response.status).toBe(401); }); @@ -247,13 +215,11 @@ describe("POST /api/auth/login", () => { const signature = testKeypair.sign(messageBytes); const signatureBase64 = signature.toString("base64"); - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: signatureBase64, - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: signatureBase64, + }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); @@ -268,13 +234,11 @@ describe("POST /api/auth/login", () => { const signature = testKeypair.sign(messageBytes); const signatureBase64 = signature.toString("base64"); - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: signatureBase64, - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: signatureBase64, + }); expect(response.status).toBe(200); expect(response.body.token).toBeDefined(); @@ -289,13 +253,11 @@ describe("POST /api/auth/login", () => { const signature = testKeypair.sign(messageBytes); const signatureBase64 = signature.toString("base64"); - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: signatureBase64, - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: signatureBase64, + }); expect(response.status).toBe(200); expect(response.headers["set-cookie"]).toBeDefined(); @@ -312,13 +274,11 @@ describe("POST /api/auth/login", () => { const wrongSignature = differentKeypair.sign(messageBytes); const wrongSignatureBase64 = wrongSignature.toString("base64"); - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: wrongSignatureBase64, - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: wrongSignatureBase64, + }); expect(response.status).toBe(401); }); @@ -332,13 +292,11 @@ describe("POST /api/auth/login", () => { const alteredMessage = `ALTERED ${message}`; - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message: alteredMessage, - signature: signatureBase64, - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message: alteredMessage, + signature: signatureBase64, + }); expect(response.status).toBe(401); }); @@ -346,13 +304,11 @@ describe("POST /api/auth/login", () => { it("should reject invalid public key format", async () => { const message = `Sign this message\n\nTimestamp: ${Date.now()}`; - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: "not_a_valid_public_key", - message, - signature: "dGVzdA==", - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: "not_a_valid_public_key", + message, + signature: "dGVzdA==", + }); expect(response.status).toBe(401); }); @@ -365,11 +321,9 @@ describe("Auth Controller - Authorization", () => { it("should allow challenge request without authentication", async () => { const testKeypair = Keypair.random(); - const response = await request(app) - .post("/api/auth/challenge") - .send({ - publicKey: testKeypair.publicKey(), - }); + const response = await request(app).post("/api/auth/challenge").send({ + publicKey: testKeypair.publicKey(), + }); expect(response.status).toBe(200); }); @@ -382,13 +336,11 @@ describe("Auth Controller - Authorization", () => { const signature = testKeypair.sign(messageBytes); const signatureBase64 = signature.toString("base64"); - const response = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: signatureBase64, - }); + const response = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: signatureBase64, + }); expect(response.status).toBe(200); }); @@ -416,13 +368,11 @@ describe("Auth Controller - Happy Path Scenarios", () => { const signature = testKeypair.sign(messageBytes); const signatureBase64 = signature.toString("base64"); - const loginResponse = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message, - signature: signatureBase64, - }); + const loginResponse = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message, + signature: signatureBase64, + }); expect(loginResponse.status).toBe(200); expect(loginResponse.body.token).toBeDefined(); @@ -448,13 +398,11 @@ describe("Auth Controller - Happy Path Scenarios", () => { const signatureBase64 = signature.toString("base64"); // Try to login with old message - const loginResponse = await request(app) - .post("/api/auth/login") - .send({ - publicKey: testKeypair.publicKey(), - message: oldMessage, - signature: signatureBase64, - }); + const loginResponse = await request(app).post("/api/auth/login").send({ + publicKey: testKeypair.publicKey(), + message: oldMessage, + signature: signatureBase64, + }); expect(loginResponse.status).toBe(401); }); @@ -481,26 +429,22 @@ describe("Auth Controller - Happy Path Scenarios", () => { const sig1 = keypair1 .sign(Buffer.from(message1, "utf-8")) .toString("base64"); - const login1 = await request(app) - .post("/api/auth/login") - .send({ - publicKey: keypair1.publicKey(), - message: message1, - signature: sig1, - }); + const login1 = await request(app).post("/api/auth/login").send({ + publicKey: keypair1.publicKey(), + message: message1, + signature: sig1, + }); // Second user login const message2 = challenge2.body.data.message; const sig2 = keypair2 .sign(Buffer.from(message2, "utf-8")) .toString("base64"); - const login2 = await request(app) - .post("/api/auth/login") - .send({ - publicKey: keypair2.publicKey(), - message: message2, - signature: sig2, - }); + const login2 = await request(app).post("/api/auth/login").send({ + publicKey: keypair2.publicKey(), + message: message2, + signature: sig2, + }); expect(login1.status).toBe(200); expect(login2.status).toBe(200); diff --git a/src/__tests__/notificationController.test.ts b/src/__tests__/notificationController.test.ts index 29f996a..d3d61d7 100644 --- a/src/__tests__/notificationController.test.ts +++ b/src/__tests__/notificationController.test.ts @@ -141,10 +141,9 @@ describe("GET /api/notifications", () => { .set(bearer(TEST_USER)); expect(response.status).toBe(200); - expect(mockNotificationService.getNotificationsForUser).toHaveBeenCalledWith( - TEST_USER, - 25, - ); + expect( + mockNotificationService.getNotificationsForUser, + ).toHaveBeenCalledWith(TEST_USER, 25); }); it("should cap limit at 100", async () => { @@ -164,10 +163,9 @@ describe("GET /api/notifications", () => { .set(bearer(TEST_USER)); expect(response.status).toBe(200); - expect(mockNotificationService.getNotificationsForUser).toHaveBeenCalledWith( - TEST_USER, - 100, - ); + expect( + mockNotificationService.getNotificationsForUser, + ).toHaveBeenCalledWith(TEST_USER, 100); }); it("should include unread count", async () => { @@ -316,9 +314,7 @@ describe("POST /api/notifications/mark-all-read", () => { expect(response.status).toBe(200); expect(response.body.success).toBe(true); - expect(mockNotificationService.markAllRead).toHaveBeenCalledWith( - TEST_USER, - ); + expect(mockNotificationService.markAllRead).toHaveBeenCalledWith(TEST_USER); }); it("should enforce user ownership - only mark own notifications", async () => { @@ -329,9 +325,7 @@ describe("POST /api/notifications/mark-all-read", () => { .set(bearer(TEST_USER)); // Service should be called with the authenticated user's ID - expect(mockNotificationService.markAllRead).toHaveBeenCalledWith( - TEST_USER, - ); + expect(mockNotificationService.markAllRead).toHaveBeenCalledWith(TEST_USER); expect(response.status).toBe(200); }); @@ -392,10 +386,9 @@ describe("GET /api/notifications/stream", () => { .set(bearer(TEST_USER)); expect(response.status).toBe(200); - expect(mockNotificationService.getNotificationsForUser).toHaveBeenCalledWith( - TEST_USER, - 50, - ); + expect( + mockNotificationService.getNotificationsForUser, + ).toHaveBeenCalledWith(TEST_USER, 50); }); it("should set correct SSE headers", async () => { @@ -418,9 +411,7 @@ describe("GET /api/notifications/stream", () => { mockNotificationService.getNotificationsForUser.mockResolvedValueOnce([]); mockNotificationService.subscribe.mockReturnValueOnce(mockUnsubscribe); - await request(app) - .get("/api/notifications/stream") - .set(bearer(TEST_USER)); + await request(app).get("/api/notifications/stream").set(bearer(TEST_USER)); expect(mockNotificationService.subscribe).toHaveBeenCalledWith( TEST_USER, @@ -454,10 +445,9 @@ describe("Notification Controller - Authorization", () => { .set(bearer(TEST_USER)); // Should be called with TEST_USER, not OTHER_USER - expect(mockNotificationService.getNotificationsForUser).toHaveBeenCalledWith( - TEST_USER, - expect.any(Number), - ); + expect( + mockNotificationService.getNotificationsForUser, + ).toHaveBeenCalledWith(TEST_USER, expect.any(Number)); }); it("should enforce user isolation on mark-read", async () => { @@ -483,9 +473,7 @@ describe("Notification Controller - Authorization", () => { .set(bearer(TEST_USER)); // Should be called with TEST_USER - expect(mockNotificationService.markAllRead).toHaveBeenCalledWith( - TEST_USER, - ); + expect(mockNotificationService.markAllRead).toHaveBeenCalledWith(TEST_USER); }); it("should enforce user isolation on stream", async () => { @@ -493,9 +481,7 @@ describe("Notification Controller - Authorization", () => { mockNotificationService.getNotificationsForUser.mockResolvedValueOnce([]); mockNotificationService.subscribe.mockReturnValueOnce(mockUnsubscribe); - await request(app) - .get("/api/notifications/stream") - .set(bearer(TEST_USER)); + await request(app).get("/api/notifications/stream").set(bearer(TEST_USER)); // Should subscribe with TEST_USER expect(mockNotificationService.subscribe).toHaveBeenCalledWith( diff --git a/src/__tests__/scoreController.test.ts b/src/__tests__/scoreController.test.ts index 757069d..4f69e9a 100644 --- a/src/__tests__/scoreController.test.ts +++ b/src/__tests__/scoreController.test.ts @@ -161,13 +161,11 @@ describe("GET /api/score/:userId", () => { // --------------------------------------------------------------------------- describe("POST /api/score/update", () => { it("should reject requests without API key", async () => { - const response = await request(app) - .post("/api/score/update") - .send({ - userId: TEST_USER, - repaymentAmount: 100, - onTime: true, - }); + const response = await request(app).post("/api/score/update").send({ + userId: TEST_USER, + repaymentAmount: 100, + onTime: true, + }); expect(response.status).toBe(401); }); @@ -267,9 +265,7 @@ describe("POST /api/score/update", () => { }); it("should invalidate cache after score update", async () => { - const { cacheService } = await import( - "../services/cacheService.js" - ); + const { cacheService } = await import("../services/cacheService.js"); const mockCacheDelete = jest.spyOn(cacheService, "delete"); mockedQuery @@ -286,9 +282,7 @@ describe("POST /api/score/update", () => { }); expect(response.status).toBe(200); - expect(mockCacheDelete).toHaveBeenCalledWith( - `score:userId:${TEST_USER}`, - ); + expect(mockCacheDelete).toHaveBeenCalledWith(`score:userId:${TEST_USER}`); mockCacheDelete.mockRestore(); }); @@ -323,7 +317,9 @@ describe("POST /api/score/update", () => { // --------------------------------------------------------------------------- describe("GET /api/score/:userId/breakdown", () => { it("should reject unauthenticated requests", async () => { - const response = await request(app).get(`/api/score/${TEST_USER}/breakdown`); + const response = await request(app).get( + `/api/score/${TEST_USER}/breakdown`, + ); expect(response.status).toBe(401); }); @@ -353,10 +349,22 @@ describe("GET /api/score/:userId/breakdown", () => { }) .mockResolvedValueOnce({ rows: [ - { event_type: "LoanRepaid", ledger_closed_at: "2025-01-01T00:00:00Z" }, - { event_type: "LoanRepaid", ledger_closed_at: "2025-01-08T00:00:00Z" }, - { event_type: "LoanRepaid", ledger_closed_at: "2025-01-15T00:00:00Z" }, - { event_type: "LoanRepaid", ledger_closed_at: "2025-01-22T00:00:00Z" }, + { + event_type: "LoanRepaid", + ledger_closed_at: "2025-01-01T00:00:00Z", + }, + { + event_type: "LoanRepaid", + ledger_closed_at: "2025-01-08T00:00:00Z", + }, + { + event_type: "LoanRepaid", + ledger_closed_at: "2025-01-15T00:00:00Z", + }, + { + event_type: "LoanRepaid", + ledger_closed_at: "2025-01-22T00:00:00Z", + }, ], }); @@ -418,9 +426,18 @@ describe("GET /api/score/:userId/breakdown", () => { }) .mockResolvedValueOnce({ rows: [ - { event_type: "LoanRepaid", ledger_closed_at: "2025-01-01T00:00:00Z" }, - { event_type: "LoanRepaid", ledger_closed_at: "2025-02-01T00:00:00Z" }, - { event_type: "LoanRepaid", ledger_closed_at: "2025-03-01T00:00:00Z" }, + { + event_type: "LoanRepaid", + ledger_closed_at: "2025-01-01T00:00:00Z", + }, + { + event_type: "LoanRepaid", + ledger_closed_at: "2025-02-01T00:00:00Z", + }, + { + event_type: "LoanRepaid", + ledger_closed_at: "2025-03-01T00:00:00Z", + }, ], });