Skip to content

Commit be3ba9c

Browse files
committed
feat(backend): increase test coverage to 80%+
1 parent edcac18 commit be3ba9c

27 files changed

Lines changed: 1110 additions & 120 deletions

backend/src/api.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ beforeAll(async () => {
4141

4242
afterAll(() => {
4343
server.close();
44-
fs.rmSync(TEST_DB_PATH, { force: true });
44+
try {
45+
fs.rmSync(TEST_DB_PATH, { force: true });
46+
} catch {
47+
// Ignore EPERM locks on Windows
48+
}
4549
});
4650

4751
beforeEach(() => {
@@ -51,8 +55,8 @@ beforeEach(() => {
5155
db.prepare(`DELETE FROM campaigns`).run();
5256
});
5357

54-
const CREATOR = `G${'A'.repeat(55)}`;
55-
const CONTRIBUTOR = `G${'B'.repeat(55)}`;
58+
const CREATOR = "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7";
59+
const CONTRIBUTOR = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI";
5660

5761
async function post(apiPath: string, body: unknown) {
5862
const response = await fetch(`${baseUrl}${apiPath}`, {

backend/src/historyEndpoint.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ let createCampaign: (typeof import('./services/campaignStore'))['createCampaign'
1919
let initCampaignStore: (typeof import('./services/campaignStore'))['initCampaignStore'];
2020
let recordEvent: (typeof import('./services/eventHistory'))['recordEvent'];
2121
let getDb: (typeof import('./services/db'))['getDb'];
22+
let resetDbForTests: (typeof import('./services/db'))['resetDbForTests'];
2223

23-
const CREATOR = `G${'A'.repeat(55)}`;
24+
const CREATOR = "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7";
2425

2526
function nowInSeconds(): number {
2627
return Math.floor(Date.now() / 1000);
@@ -31,13 +32,14 @@ beforeAll(async () => {
3132

3233
({ createCampaign, initCampaignStore } = await import('./services/campaignStore'));
3334
({ recordEvent } = await import('./services/eventHistory'));
34-
({ getDb } = await import('./services/db'));
35+
({ getDb, resetDbForTests } = await import('./services/db'));
3536
({ app } = await import('./index'));
3637

3738
initCampaignStore();
3839
});
3940

4041
afterAll(() => {
42+
resetDbForTests();
4143
fs.rmSync(TEST_DB_PATH, { force: true });
4244
});
4345

backend/src/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ let calculateProgress: CampaignStoreModule['calculateProgress'];
2121
let initCampaignStore: CampaignStoreModule['initCampaignStore'];
2222
let getDb: DbModule['getDb'];
2323

24-
const CREATOR = `G${'A'.repeat(55)}`;
25-
const CONTRIBUTOR = `G${'B'.repeat(55)}`;
24+
const CREATOR = "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7";
25+
const CONTRIBUTOR = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI";
2626

2727
beforeAll(async () => {
2828
fs.rmSync(TEST_DB_PATH, { force: true });

backend/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import compression from "compression";
22
import cors from "cors";
33
import "dotenv/config";
44
import express, { Request, Response } from "express";
5+
import helmet from "helmet";
6+
import { createServer, Server } from "http";
57

68

79
import { validateEnv } from "./validateEnv";
@@ -532,11 +534,12 @@ app.post(
532534
sendValidationError(parsedId.issues);
533535
}
534536

535-
537+
const result = reconcileOnChainPledge(parsedId.value, req.body);
536538
invalidateCampaignCache();
537539
res.status(result.existing ? 200 : 201).json({
538540
data: {
539-
541+
...result.campaign,
542+
progress: calculateProgress(result.campaign),
540543
},
541544
});
542545
},

backend/src/logger.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,19 @@ function withRequestContext(fields: LogFields): LogFields {
6262
return fields;
6363
}
6464

65+
let globalConfiguredLevel: LogLevel | null = null;
66+
export function getGlobalConfiguredLogLevel(): LogLevel {
67+
if (!globalConfiguredLevel) {
68+
globalConfiguredLevel = normalizeLogLevel(process.env.LOG_LEVEL);
69+
}
70+
return globalConfiguredLevel;
71+
}
72+
6573
export function logLine(
6674
level: LogLevel,
6775
event: string,
6876
fields: LogFields,
69-
configuredLevel: LogLevel,
77+
configuredLevel: LogLevel = getGlobalConfiguredLogLevel(),
7078
): void {
7179
if (!shouldLog(level, configuredLevel)) {
7280
return;
@@ -75,7 +83,11 @@ export function logLine(
7583
getConsoleMethod(level)(createLogLine(level, event, withRequestContext(fields)));
7684
}
7785

78-
export function logInfo(event: string, fields: LogFields, configuredLevel: LogLevel): void {
86+
export function logInfo(
87+
event: string,
88+
fields: LogFields = {},
89+
configuredLevel: LogLevel = getGlobalConfiguredLogLevel(),
90+
): void {
7991
logLine('info', event, fields, configuredLevel);
8092
}
8193

@@ -87,7 +99,7 @@ export function logRequest(
8799
status: number;
88100
durationMs: number;
89101
},
90-
configuredLevel: LogLevel,
102+
configuredLevel: LogLevel = getGlobalConfiguredLogLevel(),
91103
): void {
92104
const durationMs = Number(request.durationMs.toFixed(2));
93105

@@ -115,7 +127,7 @@ export function logError(
115127
status?: number;
116128
[key: string]: unknown;
117129
},
118-
configuredLevel: LogLevel,
130+
configuredLevel: LogLevel = getGlobalConfiguredLogLevel(),
119131
): void {
120132
const normalizedError =
121133
error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Unknown error');

backend/src/pledgesEndpoint.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ let getPledges: CampaignStoreModule['getPledges'];
2323
let getDb: DbModule['getDb'];
2424
let parsePledgeListPaginationQuery: ValidationModule['parsePledgeListPaginationQuery'];
2525

26-
const CREATOR = `G${'A'.repeat(55)}`;
27-
const CONTRIBUTOR_A = `G${'B'.repeat(55)}`;
28-
const CONTRIBUTOR_B = `G${'C'.repeat(55)}`;
26+
const CREATOR = "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7";
27+
const CONTRIBUTOR_A = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI";
28+
const CONTRIBUTOR_B = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
2929

3030
function nowInSeconds(): number {
3131
return Math.floor(Date.now() / 1000);

backend/src/rateLimiter.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe("Rate Limiter Middleware", () => {
1212
nextCalled = false;
1313
headers = {};
1414
mockReq = {
15-
ip: "127.0.0.1",
15+
ip: `127.0.0.${Math.floor(Math.random() * 253) + 2}`,
1616
method: "GET",
1717
};
1818
mockRes = {
@@ -51,18 +51,18 @@ describe("Rate Limiter Middleware", () => {
5151
const middleware = applyRateLimit(2); // Set limit to 2 for testing
5252

5353
// First request
54-
middleware(mockReq as Request, mockRes as Response, next);
54+
middleware({ ...mockReq } as Request, mockRes as Response, next);
5555
expect(nextCalled).toBe(true);
5656

5757
// Second request
5858
nextCalled = false;
59-
middleware(mockReq as Request, mockRes as Response, next);
59+
middleware({ ...mockReq } as Request, mockRes as Response, next);
6060
expect(nextCalled).toBe(true);
6161

6262
// Third request - should exceed limit
6363
nextCalled = false;
6464
expect(() => {
65-
middleware(mockReq as Request, mockRes as Response, next);
65+
middleware({ ...mockReq } as Request, mockRes as Response, next);
6666
}).toThrow(/Rate limit exceeded/);
6767
expect(headers["Retry-After"]).toBeDefined();
6868
});

backend/src/requestId.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ beforeAll(async () => {
2626
});
2727

2828
afterAll(() => {
29-
fs.rmSync(TEST_DB_PATH, { force: true });
29+
try {
30+
fs.rmSync(TEST_DB_PATH, { force: true });
31+
} catch {
32+
// Ignore EPERM locks on Windows
33+
}
3034
});
3135

3236
describe('request id middleware', () => {

backend/src/security.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
import request from 'supertest';
2-
import { describe, it, expect } from 'vitest';
2+
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
33

44
// Set environment before importing app
55
process.env.DB_PATH = ':memory:';
66
process.env.NODE_ENV = 'test';
77
process.env.CONTRACT_ID = 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
88
process.env.SOROBAN_RPC_URL = 'http://localhost:8000';
99

10+
import { initCampaignStore } from './services/campaignStore';
11+
import { config } from './config';
12+
13+
beforeAll(() => {
14+
initCampaignStore();
15+
config.contractId = 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
16+
config.sorobanRpcUrl = 'http://localhost:8000';
17+
18+
vi.spyOn(global, 'fetch').mockImplementation(() => {
19+
return Promise.resolve({
20+
ok: true,
21+
status: 200,
22+
json: () => Promise.resolve({ result: { status: 'healthy' } }),
23+
} as Response);
24+
});
25+
});
26+
27+
afterAll(() => {
28+
vi.restoreAllMocks();
29+
});
30+
1031
import { app } from './index';
1132

1233
describe('Security Headers (Helmet)', () => {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('Event Metadata Support', () => {
3434
)
3535
.run(
3636
campaignId,
37-
`G${'A'.repeat(55)}`,
37+
"GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7",
3838
`Campaign ${campaignId}`,
3939
'Synthetic campaign record for event metadata tests.',
4040
JSON.stringify(['USDC']),

0 commit comments

Comments
 (0)