diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl deleted file mode 100644 index c27d931..0000000 --- a/.beads/issues.jsonl +++ /dev/null @@ -1,7 +0,0 @@ -{"_type":"issue","id":"gh-toy-kbk","title":"Implement a help command","description":"Add a help subcommand (and --help / -h flag) that prints usage information for all available commands and flags. Acceptance: ./tool help and ./tool --help both work, lists every subcommand and flag, exit code 0.","status":"open","priority":1,"issue_type":"feature","owner":"azure-pipeline@test.com","created_at":"2026-05-12T20:47:12Z","created_by":"Azure Pipeline","updated_at":"2026-05-12T20:56:10Z","external_ref":"gh-3","labels":["e2e-testing"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"gh-toy-v6z","title":"Add input validation for empty or blank strings","description":"When a user passes an empty string or whitespace-only string as an argument, the tool should reject it with a clear error message instead of silently proceeding or crashing. Acceptance: passing empty or whitespace prints user-friendly error, non-zero exit, unit test added.","status":"open","priority":1,"issue_type":"bug","owner":"azure-pipeline@test.com","created_at":"2026-05-12T20:46:56Z","created_by":"Azure Pipeline","updated_at":"2026-05-12T20:56:10Z","external_ref":"gh-2","labels":["e2e-testing"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"gh-toy-4ef","title":"Add --version flag to CLI","description":"The CLI tool should support a --version (or -v) flag that prints the current version string and exits with code 0. Acceptance: running ./tool --version prints fleet-e2e-toy v1.0.0, exit code 0, works alongside other flags.","status":"open","priority":1,"issue_type":"feature","owner":"azure-pipeline@test.com","created_at":"2026-05-12T20:46:54Z","created_by":"Azure Pipeline","updated_at":"2026-05-12T20:56:08Z","external_ref":"gh-1","labels":["e2e-testing"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"gh-toy-24g","title":"Add config file support (~/.fleet-e2e-toy.yaml)","description":"Allow users to persist default flag values in a YAML config file at ~/.fleet-e2e-toy.yaml so they don't have to repeat common flags on every run. Acceptance: reads config on startup, CLI flags override config, warns on unknown keys, documents supported keys in README.","status":"open","priority":2,"issue_type":"feature","owner":"azure-pipeline@test.com","created_at":"2026-05-12T20:47:15Z","created_by":"Azure Pipeline","updated_at":"2026-05-12T20:47:15Z","external_ref":"gh-6","labels":["e2e-testing"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"gh-toy-69s","title":"Handle SIGINT gracefully (Ctrl-C)","description":"The tool should catch SIGINT (Ctrl-C) and exit cleanly without a Python traceback or incomplete output. Acceptance: Ctrl-C prints 'Interrupted.' and exits with code 130, no stack trace shown, partial output files cleaned up.","status":"open","priority":2,"issue_type":"feature","owner":"azure-pipeline@test.com","created_at":"2026-05-12T20:47:14Z","created_by":"Azure Pipeline","updated_at":"2026-05-12T20:47:14Z","external_ref":"gh-5","labels":["e2e-testing"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"gh-toy-aqd","title":"Add JSON output mode via --json flag","description":"Add a --json flag so all command output can be emitted as machine-readable JSON for scripting and CI pipelines. Acceptance: --json flag accepted on any subcommand, output is valid JSON, human-readable output is default, errors also JSON-formatted.","status":"open","priority":2,"issue_type":"feature","owner":"azure-pipeline@test.com","created_at":"2026-05-12T20:47:13Z","created_by":"Azure Pipeline","updated_at":"2026-05-12T20:47:13Z","external_ref":"gh-4","labels":["e2e-testing"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"gh-toy-s5k","title":"Tag filtering endpoint","description":"GET /api/notes?tag=work returns only notes with that tag. Already partially implemented - needs tests.","status":"open","priority":2,"issue_type":"feature","owner":"azure-pipeline@test.com","created_at":"2026-05-12T20:46:52Z","created_by":"Azure Pipeline","updated_at":"2026-05-12T20:46:52Z","labels":["e2e-testing"],"dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/feature_list.json b/feature_list.json index 67be6c0..144898c 100644 --- a/feature_list.json +++ b/feature_list.json @@ -1,22 +1,17 @@ [ { - "name": "Tag filtering endpoint", - "description": "GET /api/notes?tag=work returns only notes with that tag. Already partially implemented — needs tests.", + "name": "Add --version flag", + "description": "Add --version and -v flags", "passes": false }, { - "name": "Full-text search", - "description": "GET /api/notes?q=meeting searches title and content. Already partially implemented — needs edge case tests (empty query, no matches).", + "name": "Implement help command", + "description": "Implement help subcommand and flags", "passes": false }, { - "name": "Pagination support", - "description": "GET /api/notes?page=1&limit=10 returns paginated results with total count in response. Add pagination metadata: { data: [...], total: N, page: N, limit: N }.", - "passes": false - }, - { - "name": "Note archiving", - "description": "Add an 'archived' boolean field to notes. POST /api/notes/:id/archive and /api/notes/:id/unarchive endpoints. GET /api/notes excludes archived notes by default; GET /api/notes?include_archived=true includes them.", + "name": "Add input validation", + "description": "Validate empty or blank strings", "passes": false } ] diff --git a/plan.md b/plan.md deleted file mode 100644 index 682b4d7..0000000 --- a/plan.md +++ /dev/null @@ -1,32 +0,0 @@ -# Feature: NoteAPI v2 — Search, Pagination, and Archiving - -## Problem Statement -The API supports basic CRUD but lacks the query features users need for real use: filtering by tag, searching content, paginating large result sets, and archiving old notes without deleting them. - -## Approach -Add four features incrementally. Each feature is independent — no ordering dependencies. All use the existing in-memory store (no database changes). Each feature must have tests before it's considered done. - -## Phases - -### Phase 1: Tag Filtering -- [ ] GET /api/notes?tag=work returns only notes with that tag -- [ ] Tests: single tag, no match, multiple tags on same note -- Integration test: `curl localhost:3000/api/notes?tag=work` - -### Phase 2: Full-Text Search -- [ ] GET /api/notes?q=meeting searches title and content (case-insensitive) -- [ ] Tests: match in title, match in content, no match, empty query returns all -- Integration test: `curl localhost:3000/api/notes?q=meeting` - -### Phase 3: Pagination -- [ ] GET /api/notes?page=1&limit=10 returns paginated results -- [ ] Response format: `{ data: [...], total: N, page: N, limit: N }` -- [ ] Default: page 1, limit 20 -- Integration test: create 25 notes, verify page 1 has 20, page 2 has 5 - -### Phase 4: Note Archiving -- [ ] Add `archived: boolean` field to Note model (default: false) -- [ ] POST /api/notes/:id/archive and /api/notes/:id/unarchive endpoints -- [ ] GET /api/notes excludes archived by default -- [ ] GET /api/notes?include_archived=true includes them -- Integration test: archive a note, verify it's hidden, unarchive, verify it's back diff --git a/src/index.ts b/src/index.ts index 9e7603c..035fac8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,30 @@ +const args = process.argv.slice(2); +for (const arg of args) { + if (arg.trim() === '') { + console.error('Error: Invalid argument. Arguments cannot be empty or whitespace-only.'); + process.exit(1); + } +} + +if (process.argv.includes('--version') || process.argv.includes('-v')) { + console.log('fleet-e2e-toy v1.0.0'); + process.exit(0); +} + +const helpMessage = `Usage: fleet-e2e-toy [command] [options] + +Commands: + help Display help information + +Options: + -v, --version Display version information + -h, --help Display help information`; + +if (process.argv.includes('help') || process.argv.includes('--help') || process.argv.includes('-h')) { + console.log(helpMessage); + process.exit(0); +} + import app from "./app"; const PORT = process.env.PORT ?? 3000; diff --git a/tests/cli.test.ts b/tests/cli.test.ts new file mode 100644 index 0000000..4e38515 --- /dev/null +++ b/tests/cli.test.ts @@ -0,0 +1,57 @@ +import { execSync } from 'child_process'; +import path from 'path'; + +const tsNode = path.resolve(__dirname, '../node_modules/.bin/ts-node'); +const entryPoint = path.resolve(__dirname, '../src/index.ts'); + +describe('CLI --version', () => { + it('prints the correct version and exits 0', () => { + const output = execSync(`"${tsNode}" "${entryPoint}" --version`, { encoding: 'utf8', env: { ...process.env, PORT: '0' } }); + expect(output.trim()).toBe('fleet-e2e-toy v1.0.0'); + }); + + it('prints the correct version with -v and exits 0', () => { + const output = execSync(`"${tsNode}" "${entryPoint}" -v`, { encoding: 'utf8', env: { ...process.env, PORT: '0' } }); + expect(output.trim()).toBe('fleet-e2e-toy v1.0.0'); + }); +}); + +describe('CLI help', () => { + const expectedHelp = `Usage: fleet-e2e-toy [command] [options] + +Commands: + help Display help information + +Options: + -v, --version Display version information + -h, --help Display help information`; + + it('prints help information with --help', () => { + const output = execSync(`"${tsNode}" "${entryPoint}" --help`, { encoding: 'utf8', env: { ...process.env, PORT: '0' } }); + expect(output.trim()).toBe(expectedHelp); + }); + + it('prints help information with -h', () => { + const output = execSync(`"${tsNode}" "${entryPoint}" -h`, { encoding: 'utf8', env: { ...process.env, PORT: '0' } }); + expect(output.trim()).toBe(expectedHelp); + }); + + it('prints help information with help subcommand', () => { + const output = execSync(`"${tsNode}" "${entryPoint}" help`, { encoding: 'utf8', env: { ...process.env, PORT: '0' } }); + expect(output.trim()).toBe(expectedHelp); + }); +}); + +describe('CLI input validation', () => { + it('rejects empty string argument', () => { + expect(() => { + execSync(`"${tsNode}" "${entryPoint}" ""`, { encoding: 'utf8', env: { ...process.env, PORT: '0' }, stdio: 'pipe' }); + }).toThrow(); + }); + + it('rejects whitespace-only string argument', () => { + expect(() => { + execSync(`"${tsNode}" "${entryPoint}" " "`, { encoding: 'utf8', env: { ...process.env, PORT: '0' }, stdio: 'pipe' }); + }).toThrow(); + }); +});