Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/backend/lambdas/donors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Lambda for managing donors.
|--------|------|-------------|
| GET | /health | Health check |
| GET | /donors | |
| GET | /donations | |
| POST | /donations | |
| POST | /donors | |

## Setup
Expand Down
38 changes: 38 additions & 0 deletions apps/backend/lambdas/donors/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,44 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
return json(200, { data: donations });
}

// POST /donations
if (normalizedPath === '/donations' && method === 'POST') {
const body = event.body ? JSON.parse(event.body) as Record<string, unknown> : {};
const { donor_id, project_id, amount } = body;

if (donor_id === undefined || project_id === undefined || amount === undefined) {
return json(400, { message: 'donor_id, project_id, and amount are required' });
}
if (!Number.isInteger(donor_id) || (donor_id as number) < 1) {
return json(400, { message: 'donor_id must be a positive integer' });
}
if (!Number.isInteger(project_id) || (project_id as number) < 1) {
return json(400, { message: 'project_id must be a positive integer' });
}
if (typeof amount !== 'number' || amount <= 0 || !isFinite(amount)) {
return json(400, { message: 'amount must be a positive number' });
}

try {
const donation = await db
.insertInto('branch.project_donations')
.values({
donor_id: donor_id as number,
project_id: project_id as number,
amount: amount as number,
})
.returningAll()
.executeTakeFirstOrThrow();

return json(201, { data: donation });
} catch (err: any) {
if (err?.code === '23505') {
return json(409, { message: 'A donation from this donor to this project already exists' });
}
throw err;
}
}

// POST /donors
if ((normalizedPath === '/' || normalizedPath === '/donors') && method === 'POST') {
const body = event.body ? JSON.parse(event.body) as Record<string, unknown> : {};
Expand Down
35 changes: 35 additions & 0 deletions apps/backend/lambdas/donors/test/donors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,41 @@ describe("Donor API with data", () => {
const res = await handler(createEvent('GET', '/donations'));
expect(res.statusCode).toBe(401);
});
test("POST /donations returns 201 and created donation", async () => {
mockAuthenticateRequest.mockResolvedValueOnce(authenticatedUser);
const res = await handler(createEvent('POST', '/donations', {
donor_id: 1, project_id: 4, amount: 500,
}));
const body = JSON.parse(res.body);
expect(res.statusCode).toBe(201);
expect(body.data.donor_id).toBe(1);
expect(body.data.project_id).toBe(4);
expect(Number(body.data.amount)).toBe(500);
});

test("POST /donations returns 400 when donor_id is missing", async () => {
mockAuthenticateRequest.mockResolvedValueOnce(authenticatedUser);
const res = await handler(createEvent('POST', '/donations', { project_id: 1, amount: 100 }));
expect(res.statusCode).toBe(400);
});

test("POST /donations returns 400 when amount is zero", async () => {
mockAuthenticateRequest.mockResolvedValueOnce(authenticatedUser);
const res = await handler(createEvent('POST', '/donations', { donor_id: 1, project_id: 1, amount: 0 }));
expect(res.statusCode).toBe(400);
});

test("POST /donations returns 400 when amount is negative", async () => {
mockAuthenticateRequest.mockResolvedValueOnce(authenticatedUser);
const res = await handler(createEvent('POST', '/donations', { donor_id: 1, project_id: 1, amount: -50 }));
expect(res.statusCode).toBe(400);
});

test("POST /donations returns 401 when unauthenticated", async () => {
mockAuthenticateRequest.mockResolvedValueOnce({ isAuthenticated: false });
const res = await handler(createEvent('POST', '/donations', { donor_id: 1, project_id: 1, amount: 100 }));
expect(res.statusCode).toBe(401);
});
});

describe("Donor API when DB is empty", () => {
Expand Down
Loading