From 862f8fbb827eeee2d4e8111932aed165d4b21dfb Mon Sep 17 00:00:00 2001 From: fleet-reviewer Date: Wed, 13 May 2026 17:35:49 -0400 Subject: [PATCH 1/3] feat(cli): add --version and --help flags, reject empty-string note inputs --- src/index.ts | 24 +++++++++++++++++ src/utils/validation.ts | 12 ++++----- tests/notes.test.ts | 48 ++++++++++++++++++++++++++++++++++ tests/validation.test.ts | 56 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9e7603c..032c7fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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, () => { diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 9cfb289..87eef61 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -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) { @@ -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) ?? [], }, }; @@ -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) { @@ -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 }; diff --git a/tests/notes.test.ts b/tests/notes.test.ts index 45553cf..7127014 100644 --- a/tests/notes.test.ts +++ b/tests/notes.test.ts @@ -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", () => { @@ -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", () => { diff --git a/tests/validation.test.ts b/tests/validation.test.ts index f55f70f..e334e30 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -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", () => { @@ -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"); + } + }); }); From fd03cdde9ea04de92c2d2747660b12eca0f2b8b5 Mon Sep 17 00:00:00 2001 From: fleet-reviewer Date: Wed, 13 May 2026 17:43:34 -0400 Subject: [PATCH 2/3] review: e2e-s1.2 cli-features (APPROVED) --- feedback.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 feedback.md diff --git a/feedback.md b/feedback.md new file mode 100644 index 0000000..7bbc907 --- /dev/null +++ b/feedback.md @@ -0,0 +1,27 @@ +# Review — e2e-s1.2-25826536670/cli-features + +**Verdict:** APPROVED + +## Quality gates +- Lint: PASS +- Tests: 33/33 PASS +- Build: PASS + +## Acceptance criteria +- gh-toy-4ef --version: PASS — --version and -v both print exactly fleet-e2e-toy v1.0.0 to stdout, exit 0, and do not start the HTTP server. Works alongside other flags. +- gh-toy-kbk help: PASS — --help, -h, and help subcommand all print usage info listing every flag, subcommand, and the PORT env var. All exit 0 without starting the server. +- gh-toy-v6z empty/whitespace: PASS — Empty and whitespace-only strings for both title and content are rejected with 400 and clear error messages on both create and update endpoints. Unit tests in validation.test.ts (6 new tests) and integration tests in notes.test.ts (5 new tests) cover all cases. + +## Findings +### HIGH +- (none) +### MEDIUM +- Version string is hardcoded in src/index.ts:6 rather than derived from package.json version field. If package.json version is bumped without updating index.ts, the CLI output will drift. +### LOW +- Help text says Usage: node src/index.ts but the built entrypoint is dist/index.js. Minor inconsistency. +- import app executes before flag checks, so Express loads even for --version/--help. No functional impact but adds startup overhead for info-only flags. + +## Notes +- Clean, well-structured implementation. Flag handling before server startup is the right pattern. +- Test coverage is thorough for all new validation paths. +- Content validation change is technically a breaking API change (empty content was previously allowed on create), but this matches the issue requirements. From 8d9c8f54c0cff452c26304e2aebe8f3394f585ac Mon Sep 17 00:00:00 2001 From: fleet-reviewer Date: Wed, 13 May 2026 17:44:58 -0400 Subject: [PATCH 3/3] cleanup: remove fleet control files --- feedback.md | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 feedback.md diff --git a/feedback.md b/feedback.md deleted file mode 100644 index 7bbc907..0000000 --- a/feedback.md +++ /dev/null @@ -1,27 +0,0 @@ -# Review — e2e-s1.2-25826536670/cli-features - -**Verdict:** APPROVED - -## Quality gates -- Lint: PASS -- Tests: 33/33 PASS -- Build: PASS - -## Acceptance criteria -- gh-toy-4ef --version: PASS — --version and -v both print exactly fleet-e2e-toy v1.0.0 to stdout, exit 0, and do not start the HTTP server. Works alongside other flags. -- gh-toy-kbk help: PASS — --help, -h, and help subcommand all print usage info listing every flag, subcommand, and the PORT env var. All exit 0 without starting the server. -- gh-toy-v6z empty/whitespace: PASS — Empty and whitespace-only strings for both title and content are rejected with 400 and clear error messages on both create and update endpoints. Unit tests in validation.test.ts (6 new tests) and integration tests in notes.test.ts (5 new tests) cover all cases. - -## Findings -### HIGH -- (none) -### MEDIUM -- Version string is hardcoded in src/index.ts:6 rather than derived from package.json version field. If package.json version is bumped without updating index.ts, the CLI output will drift. -### LOW -- Help text says Usage: node src/index.ts but the built entrypoint is dist/index.js. Minor inconsistency. -- import app executes before flag checks, so Express loads even for --version/--help. No functional impact but adds startup overhead for info-only flags. - -## Notes -- Clean, well-structured implementation. Flag handling before server startup is the right pattern. -- Test coverage is thorough for all new validation paths. -- Content validation change is technically a breaking API change (empty content was previously allowed on create), but this matches the issue requirements.