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
24 changes: 24 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
import app from "./app";

const args = process.argv.slice(2);

if (args.includes("--version") || args.includes("-v")) {
process.stdout.write("fleet-e2e-toy v1.0.0\n");
process.exit(0);
}

if (args.includes("--help") || args.includes("-h") || args[0] === "help") {
process.stdout.write(
"Usage: node src/index.ts [options]\n" +
"\n" +
"Options:\n" +
" --version, -v Print version and exit\n" +
" --help, -h Print this help message and exit\n" +
"\n" +
"Subcommands:\n" +
" help Print this help message and exit\n" +
"\n" +
"Environment Variables:\n" +
" PORT Port to listen on (default: 3000)\n"
);
process.exit(0);
}

const PORT = process.env.PORT ?? 3000;

app.listen(PORT, () => {
Expand Down
12 changes: 6 additions & 6 deletions src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export function validateCreateInput(
errors.push({ field: "title", message: "Title is required and must be a non-empty string" });
}

if (typeof obj.content !== "string") {
errors.push({ field: "content", message: "Content must be a string" });
if (typeof obj.content !== "string" || obj.content.trim().length === 0) {
errors.push({ field: "content", message: "Content is required and must be a non-empty string" });
}

if (obj.tags !== undefined) {
Expand All @@ -36,7 +36,7 @@ export function validateCreateInput(
valid: true,
data: {
title: (obj.title as string).trim(),
content: obj.content as string,
content: (obj.content as string).trim(),
tags: (obj.tags as string[] | undefined) ?? [],
},
};
Expand All @@ -57,8 +57,8 @@ export function validateUpdateInput(
errors.push({ field: "title", message: "Title must be a non-empty string" });
}

if (obj.content !== undefined && typeof obj.content !== "string") {
errors.push({ field: "content", message: "Content must be a string" });
if (obj.content !== undefined && (typeof obj.content !== "string" || obj.content.trim().length === 0)) {
errors.push({ field: "content", message: "Content must be a non-empty string" });
}

if (obj.tags !== undefined) {
Expand All @@ -71,7 +71,7 @@ export function validateUpdateInput(

const data: UpdateNoteInput = {};
if (obj.title !== undefined) data.title = (obj.title as string).trim();
if (obj.content !== undefined) data.content = obj.content as string;
if (obj.content !== undefined) data.content = (obj.content as string).trim();
if (obj.tags !== undefined) data.tags = obj.tags as string[];

return { valid: true, data };
Expand Down
48 changes: 48 additions & 0 deletions tests/notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,30 @@ describe("POST /api/notes", () => {
expect(res.status).toBe(400);
expect(res.body.errors).toBeDefined();
});

it("returns 400 for whitespace-only title", async () => {
const res = await request(app)
.post("/api/notes")
.send({ title: " ", content: "Body" });
expect(res.status).toBe(400);
expect(res.body.errors).toBeDefined();
});

it("returns 400 for empty content", async () => {
const res = await request(app)
.post("/api/notes")
.send({ title: "Note", content: "" });
expect(res.status).toBe(400);
expect(res.body.errors).toBeDefined();
});

it("returns 400 for whitespace-only content", async () => {
const res = await request(app)
.post("/api/notes")
.send({ title: "Note", content: " " });
expect(res.status).toBe(400);
expect(res.body.errors).toBeDefined();
});
});

describe("PUT /api/notes/:id", () => {
Expand All @@ -110,6 +134,30 @@ describe("PUT /api/notes/:id", () => {
.send({ title: "Nope" });
expect(res.status).toBe(404);
});

it("returns 400 for whitespace-only title on update", async () => {
const create = await request(app)
.post("/api/notes")
.send({ title: "Original", content: "Body", tags: [] });

const res = await request(app)
.put(`/api/notes/${create.body.id}`)
.send({ title: " " });
expect(res.status).toBe(400);
expect(res.body.errors).toBeDefined();
});

it("returns 400 for whitespace-only content on update", async () => {
const create = await request(app)
.post("/api/notes")
.send({ title: "Original", content: "Body", tags: [] });

const res = await request(app)
.put(`/api/notes/${create.body.id}`)
.send({ content: " " });
expect(res.status).toBe(400);
expect(res.body.errors).toBeDefined();
});
});

describe("DELETE /api/notes/:id", () => {
Expand Down
56 changes: 56 additions & 0 deletions tests/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,38 @@ describe("validateCreateInput", () => {
expect(result.errors[0].field).toBe("tags");
}
});

it("rejects whitespace-only title", () => {
const result = validateCreateInput({ title: " ", content: "Body" });
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors[0].field).toBe("title");
}
});

it("rejects empty content", () => {
const result = validateCreateInput({ title: "Note", content: "" });
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors[0].field).toBe("content");
}
});

it("rejects whitespace-only content", () => {
const result = validateCreateInput({ title: "Note", content: " " });
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors[0].field).toBe("content");
}
});

it("trims content on valid input", () => {
const result = validateCreateInput({ title: "Note", content: " Body " });
expect(result.valid).toBe(true);
if (result.valid) {
expect(result.data.content).toBe("Body");
}
});
});

describe("validateUpdateInput", () => {
Expand All @@ -63,4 +95,28 @@ describe("validateUpdateInput", () => {
const result = validateUpdateInput({});
expect(result.valid).toBe(true);
});

it("rejects whitespace-only title", () => {
const result = validateUpdateInput({ title: " " });
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors[0].field).toBe("title");
}
});

it("rejects empty content string", () => {
const result = validateUpdateInput({ content: "" });
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors[0].field).toBe("content");
}
});

it("rejects whitespace-only content string", () => {
const result = validateUpdateInput({ content: " " });
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors[0].field).toBe("content");
}
});
});
Loading