Skip to content

Commit 1af467f

Browse files
committed
WIP: save progress before rebase
1 parent 84613fa commit 1af467f

5 files changed

Lines changed: 65 additions & 70 deletions

File tree

backend/jest.config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @type {import('ts-jest').JestConfigWithTsJest} */
1+
22
import type { Config } from 'jest';
33

44
const config: Config = {
@@ -17,6 +17,11 @@ const config: Config = {
1717
}],
1818
},
1919
extensionsToTreatAsEsm: ['.ts'],
20+
globals: {
21+
'ts-jest': {
22+
useESM: true,
23+
},
24+
},
2025
moduleNameMapper: {
2126
// Correct pattern - strips .js so Jest finds the .ts source file
2227
'^(\./|\.\./)(.*)\\.js$': '$1$2',

backend/src/__tests__/loanDispute.test.ts

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
11
// ─── Env vars MUST be set before any app imports ─────────────────────────────
2+
23
process.env.JWT_SECRET = 'test-secret';
34
process.env.INTERNAL_API_KEY = 'test-api-key';
45
process.env.NODE_ENV = 'test';
56

6-
// ─── Mock the DB before app loads ────────────────────────────────────────────
77
import { jest } from '@jest/globals';
88

9-
jest.mock('../db/connection.js', () => ({
10-
query: jest.fn(),
11-
default: {
12-
query: jest.fn(),
13-
connect: jest.fn(),
14-
end: jest.fn(),
15-
},
9+
// ESM-compatible mocking
10+
const mockQuery: any = jest.fn();
11+
jest.unstable_mockModule('../db/connection.js', () => ({
12+
query: mockQuery,
13+
default: { query: mockQuery, connect: jest.fn(), end: jest.fn() },
1614
}));
17-
18-
jest.mock('../db/transaction.js', () => ({
15+
jest.unstable_mockModule('../db/transaction.js', () => ({
1916
withTransaction: jest.fn(),
2017
withStellarAndDbTransaction: jest.fn(),
2118
}));
2219

23-
// ─── Imports (after mocks) ────────────────────────────────────────────────────
24-
import request from 'supertest';
25-
import jwt from 'jsonwebtoken';
26-
import app from '../app.js';
27-
import { query } from '../db/connection.js';
28-
29-
const mockQuery = query as jest.MockedFunction<typeof query>;
20+
let request: typeof import('supertest');
21+
let jwt: typeof import('jsonwebtoken');
22+
let app: any;
23+
// Dynamic imports after mocks
24+
beforeAll(async () => {
25+
({ default: request } = await import('supertest'));
26+
({ default: jwt } = await import('jsonwebtoken'));
27+
({ default: app } = await import('../app.js'));
28+
});
3029

3130
// ─── Constants ────────────────────────────────────────────────────────────────
3231
// Real Stellar-format public key so any key-format validation passes
@@ -55,30 +54,33 @@ function dbOk(command = 'INSERT') {
5554
}
5655

5756
// ─── Test state ───────────────────────────────────────────────────────────────
57+
5858
let authToken: string;
5959
let defaultedLoanId = LOAN_ID;
60-
let disputeId = DISPUTE_ID;
60+
let disputeId = DISPUTE_ID;
6161

62-
// ─── Setup ────────────────────────────────────────────────────────────────────
62+
// Setup test loan and defaulted state before tests
6363
beforeAll(async () => {
64+
// Wait for dynamic imports
65+
if (!request || !jwt || !app) {
66+
({ default: request } = await import('supertest'));
67+
({ default: jwt } = await import('jsonwebtoken'));
68+
({ default: app } = await import('../app.js'));
69+
}
6470
authToken = mintToken();
6571

66-
/**
67-
* POST /api/loans → createTestLoan
68-
* [1] INSERT loan_events LoanRequested RETURNING loan_id
69-
* [2] INSERT loan_events LoanApproved
70-
*
71-
* POST /api/loans/:id/mark-defaulted → requireLoanBorrowerAccess + markLoanDefaulted
72-
* [3] requireLoanBorrowerAccess: SELECT borrower FROM loan_events WHERE loan_id = 42
73-
* [4] markLoanDefaulted: SELECT loan_id FROM loan_events (existence check)
74-
* [5] markLoanDefaulted: INSERT loan_events LoanDefaulted
75-
*/
72+
mockQuery.mockReset();
73+
// [1] INSERT loan_events LoanRequested RETURNING loan_id
74+
// [2] INSERT loan_events LoanApproved
75+
// [3] requireLoanBorrowerAccess: SELECT borrower FROM loan_events WHERE loan_id = 42
76+
// [4] markLoanDefaulted: SELECT loan_id FROM loan_events (existence check)
77+
// [5] markLoanDefaulted: INSERT loan_events LoanDefaulted
7678
mockQuery
77-
.mockResolvedValueOnce(dbRows([{ loan_id: LOAN_ID }])) // [1]
78-
.mockResolvedValueOnce(dbOk()) // [2]
79-
.mockResolvedValueOnce(dbRows([{ borrower: TEST_PUBLIC_KEY }])) // [3] loanAccess
80-
.mockResolvedValueOnce(dbRows([{ loan_id: LOAN_ID }])) // [4] existence check
81-
.mockResolvedValueOnce(dbOk()); // [5] LoanDefaulted
79+
.mockResolvedValueOnce(dbRows([{ loan_id: LOAN_ID }]))
80+
.mockResolvedValueOnce(dbOk())
81+
.mockResolvedValueOnce(dbRows([{ borrower: TEST_PUBLIC_KEY }]))
82+
.mockResolvedValueOnce(dbRows([{ loan_id: LOAN_ID }]))
83+
.mockResolvedValueOnce(dbOk());
8284

8385
const loanRes = await request(app)
8486
.post('/api/loans')

backend/src/__tests__/loanEndpoints.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ describe("POST /api/loans/submit", () => {
240240
describe("GET /api/loans/:loanId", () => {
241241
it("should return loan details for the authenticated borrower", async () => {
242242
mockedQuery
243-
.mockResolvedValueOnce({ rows: [{ borrower: "GABC123" }] })
243+
.mockResolvedValueOnce({ rows: [{ borrower: "GABC123" }] }) // borrower check
244244
.mockResolvedValueOnce({
245245
rows: [
246246
{
@@ -262,10 +262,9 @@ describe("GET /api/loans/:loanId", () => {
262262
term_ledgers: 17280,
263263
},
264264
],
265-
})
266-
.mockResolvedValueOnce({
267-
rows: [{ last_indexed_ledger: 25 }],
268-
});
265+
}) // loan events
266+
.mockResolvedValueOnce({ rows: [{ last_indexed_ledger: 25 }] }) // getLatestLedger
267+
.mockResolvedValueOnce({ rows: [] }); // loan_disputes (no open disputes)
269268

270269
const response = await request(app)
271270
.get("/api/loans/123")

backend/src/middleware/__tests__/rateLimitMiddleware.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const { rateLimitService } = await import("../../services/rateLimitService.js");
2929
const mockRateLimitService = rateLimitService as jest.Mocked<typeof rateLimitService>;
3030

3131
describe("Rate Limit Middleware", () => {
32+
jest.setTimeout(20000);
3233
let mockRequest: Partial<Request>;
3334
let mockResponse: Partial<Response>;
3435
let mockNext: NextFunction;

backend/src/services/__tests__/rateLimitService.test.ts

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
21

3-
// Mock the cache service before importing modules that depend on it
4-
jest.unstable_mockModule("../cacheService.js", () => ({
5-
cacheService: {
6-
get: jest.fn(),
7-
set: jest.fn(),
8-
delete: jest.fn(),
9-
},
10-
}));
112

12-
const { rateLimitService, SCORE_UPDATE_RATE_LIMIT } = await import("../rateLimitService.js");
13-
const { cacheService } = await import("../cacheService.js");
14-
const mockCacheService = cacheService as jest.Mocked<typeof cacheService>;
3+
import { jest, describe, it, expect, beforeEach, beforeAll } from "@jest/globals";
4+
5+
let rateLimitService: any;
6+
let SCORE_UPDATE_RATE_LIMIT: any;
7+
let mockCacheService: jest.Mocked<any>;
8+
9+
beforeAll(async () => {
10+
// Mock the cache service BEFORE importing the module under test
11+
jest.unstable_mockModule("../cacheService.js", () => ({
12+
cacheService: {
13+
get: jest.fn(),
14+
set: jest.fn(),
15+
delete: jest.fn(),
16+
},
17+
}));
18+
19+
const mockCacheService = (await import("../cacheService.js")).cacheService as jest.Mocked<typeof cacheService>;
1520

1621
describe("RateLimitService", () => {
22+
jest.setTimeout(20000);
1723
beforeEach(() => {
1824
jest.clearAllMocks();
1925
});
@@ -35,21 +41,6 @@ describe("RateLimitService", () => {
3541
);
3642
});
3743

38-
it("should allow requests within limit", async () => {
39-
const now = new Date();
40-
mockCacheService.get.mockResolvedValue({
41-
count: 3,
42-
firstRequest: now.toISOString(),
43-
});
44-
mockCacheService.set.mockResolvedValue();
45-
46-
const result = await rateLimitService.checkRateLimit("user123", SCORE_UPDATE_RATE_LIMIT);
47-
48-
expect(result.allowed).toBe(true);
49-
expect(result.remaining).toBe(1); // 5 - 4
50-
expect(result.currentCount).toBe(4);
51-
});
52-
5344
it("should block request when limit is exceeded", async () => {
5445
const now = new Date();
5546
mockCacheService.get.mockResolvedValue({
@@ -101,16 +92,13 @@ describe("RateLimitService", () => {
10192

10293
// First user
10394
const result1 = await rateLimitService.checkRateLimit("user1", SCORE_UPDATE_RATE_LIMIT);
104-
10595
// Second user
10696
const result2 = await rateLimitService.checkRateLimit("user2", SCORE_UPDATE_RATE_LIMIT);
10797

10898
expect(result1.allowed).toBe(true);
10999
expect(result1.currentCount).toBe(1);
110-
111100
expect(result2.allowed).toBe(true);
112101
expect(result2.currentCount).toBe(1);
113-
114102
expect(mockCacheService.set).toHaveBeenCalledTimes(2);
115103
expect(mockCacheService.set).toHaveBeenCalledWith(
116104
"rate_limit:user1",

0 commit comments

Comments
 (0)