Skip to content

Commit f591ec6

Browse files
committed
Validate notification creation requests
1 parent b55e6bb commit f591ec6

3 files changed

Lines changed: 77 additions & 2 deletions

File tree

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
import { ok } from "../utils/response.js";
1+
import { createNotificationSchema } from "../validators/notification.js";
2+
import { fail, ok } from "../utils/response.js";
23
import { createNotification, listNotifications } from "../services/notificationService.js";
34

45
export async function getNotifications(req, res) {
56
return ok(res, await listNotifications());
67
}
78

89
export async function postNotification(req, res) {
9-
return ok(res, await createNotification(req.body), 201);
10+
const payload = createNotificationSchema.safeParse(req.body);
11+
12+
if (!payload.success) {
13+
return fail(res, "Invalid notification request", 400);
14+
}
15+
16+
return ok(res, await createNotification(payload.data), 201);
1017
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import test from "node:test";
2+
import assert from "node:assert/strict";
3+
import { createApp } from "../app.js";
4+
5+
async function withServer(run) {
6+
const app = createApp();
7+
const server = app.listen(0);
8+
9+
await new Promise((resolve, reject) => {
10+
server.once("listening", resolve);
11+
server.once("error", reject);
12+
});
13+
14+
try {
15+
const { port } = server.address();
16+
await run(`http://127.0.0.1:${port}`);
17+
} finally {
18+
await new Promise((resolve, reject) => {
19+
server.close((error) => (error ? reject(error) : resolve()));
20+
});
21+
}
22+
}
23+
24+
test("POST /api/notifications rejects blank titles", async () => {
25+
await withServer(async (baseUrl) => {
26+
const response = await fetch(`${baseUrl}/api/notifications`, {
27+
method: "POST",
28+
headers: { "content-type": "application/json" },
29+
body: JSON.stringify({
30+
userId: "usr_client",
31+
title: " ",
32+
body: "Proposal updated"
33+
})
34+
});
35+
const payload = await response.json();
36+
37+
assert.equal(response.status, 400);
38+
assert.equal(payload.success, false);
39+
});
40+
});
41+
42+
test("POST /api/notifications accepts valid notification payloads", async () => {
43+
await withServer(async (baseUrl) => {
44+
const response = await fetch(`${baseUrl}/api/notifications`, {
45+
method: "POST",
46+
headers: { "content-type": "application/json" },
47+
body: JSON.stringify({
48+
userId: "usr_client",
49+
title: "Proposal updated",
50+
body: "A freelancer submitted a proposal"
51+
})
52+
});
53+
const payload = await response.json();
54+
55+
assert.equal(response.status, 201);
56+
assert.equal(payload.success, true);
57+
assert.equal(payload.data.userId, "usr_client");
58+
assert.equal(payload.data.title, "Proposal updated");
59+
assert.equal(payload.data.body, "A freelancer submitted a proposal");
60+
});
61+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { z } from "zod";
2+
3+
export const createNotificationSchema = z.object({
4+
userId: z.string().min(1),
5+
title: z.string().trim().min(1),
6+
body: z.string().trim().min(1)
7+
});

0 commit comments

Comments
 (0)