From 567cc8b1e21af556e63484e763dc0e22b7a7f4c4 Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Sun, 14 Jun 2026 21:46:16 -0400 Subject: [PATCH] tests: Added plan for improving testing. --- TEST_PLAN.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++ TODOS.md | 32 +++++++------- cmd_version.odin | 6 +-- 3 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 TEST_PLAN.md diff --git a/TEST_PLAN.md b/TEST_PLAN.md new file mode 100644 index 0000000..292ddce --- /dev/null +++ b/TEST_PLAN.md @@ -0,0 +1,106 @@ +# Test Coverage Plan + +## Current State + +- 60 tests, all passing +- Strong coverage: crypto (100%), ssh (80%), scan, features +- Misleading test files: `cmd_check_test`, `cmd_list_test`, `cmd_nushell_completion_test` don't test their namesake procs +- Biggest gap: `db.odin` (15/21 procs untested), all `cmd_*` handlers untested, `parse_args` untested + +## Tier 1 — Easy wins (pure functions, minimal setup) + +### 1. `render_table` (table.odin) +- Follow existing `render_json_rows` test pattern +- Test cases: normal data (verify box-drawing chars, column alignment), empty rows, wide unicode, single column +- Assert against `strings.Builder` output + +### 2. `parse_args` (cli.odin) +- Test cases: bare command, `--flag value`, `-f value`, positional args, `--help`/`-h`, unknown command, no args (prints usage), flag without value (error) +- High value — this is the entry point for all command dispatch + +### 3. `is_encrypted_key` (ssh.odin) +- Test cases: encrypted key (returns true), unencrypted key (returns false), RSA key, malformed key +- Fills last gap in ssh.odin + +## Tier 2 — High value, medium effort (fixtures exist) + +### 4. `db.odin` CRUD layer +Largest gap in the project. Infrastructure already in `db_integration_test.odin` (`fixture_key`, `fixture_db_path`, in-memory DB setup). + +Procs to test: +- `db_open` / `db_close` — open in-memory DB, verify handle valid +- `db_insert` — insert a row, verify it persists +- `db_fetch` — fetch existing row, fetch missing row (returns false) +- `db_delete` — delete existing row (returns true), delete missing row (returns false) +- `db_list` — list multiple rows, empty DB +- `db_vacuum_to_file` — vacuum to temp file, verify file exists and is non-empty + +Test pattern: create in-memory DB via `db_open`, insert fixture rows, query and assert, `defer db_close`. + +### 5. `load_config` / `save_config` (config.odin) +- `save_config`: write a `Config` to temp dir, verify file exists and contents are valid JSON +- `load_config`: read back a config written by `save_config`, round-trip assert +- `load_config` error case: missing file returns error +- Need a temp dir fixture (pattern exists in `scan_test.odin`) + +## Tier 3 — Command handlers (need DB + filesystem fixtures) + +### 6. `cmd_version` (cmd_version.odin) +- Test default output (prints VERSION) +- Test `--long`/`-l` flag output +- Capture stdout, assert content + +### 7. `cmd_list` (cmd_list.odin) +- Test TTY path: fixture DB with rows, capture table output +- Test non-TTY path: capture JSON output, unmarshal and verify keys/values +- Test empty DB: verify clean output (empty table or `[]`) + +### 8. `cmd_backup` (cmd_backup.odin) +- Test successful backup: valid path, verify `db_insert` called +- Test missing file: verify error message +- Test duplicate backup: verify rejection or update behavior + +### 9. `cmd_remove` (cmd_remove.odin) +- Test successful removal: existing entry, verify `db_delete` called +- Test removal of non-existent entry: verify error or no-op + +### 10. `cmd_restore` (cmd_restore.odin) +- Test successful restore: entry exists in DB, verify file written to correct path +- Test restore of missing entry: verify error +- Test directory creation: restore to path with missing parent dirs + +## Tier 4 — Hard to test (interactive / external deps) + +### 11. `cmd_deps` (cmd_deps.odin) +- Needs `git` and/or `fd` in PATH +- Test TTY and non-TTY paths +- Skip if dependencies not available (with `#assert` like TODO 28 suggests) + +### 12. `cmd_scan` (cmd_scan.odin) +- Needs `fd` installed +- Test with fixture git repo containing `.env` files +- Test `find_unbacked` integration (already partially tested in `cmd_check_test.odin`) +- Non-TTY JSON output path + +### 13. `cmd_edit_config` (cmd_edit_config.odin) +- Needs refactoring: extract `$EDITOR` parsing into testable helper (TODO 12) +- Test multi-word editor values (`"code -w"`) +- Test missing `$EDITOR` + +### 14. `cmd_init` (cmd_init.odin) +- Interactive prompt makes this hard +- Needs refactoring: extract SSH key discovery and config generation into testable procs +- Test `--force` flag behavior + +### 15. `prompt.odin` +- Needs refactoring to be testable +- `render_options` could be tested if it accepted an `io.Writer` +- `read_key` could be tested with a pipe/redirect instead of raw stdin +- `multi_select` is end-to-end interactive, likely integration test only + +## Notes + +- All command handler tests will need stdout capture. Consider extracting a helper or using `io.Writer` injection. +- DB integration tests should use in-memory SQLite (`:memory:`) where possible. +- Temp dir fixtures should follow the pattern in `scan_test.odin`. +- External dependency tests (`fd`, `git`) should use `#assert` to ensure the dependency is present rather than silently skipping (TODO 28). diff --git a/TODOS.md b/TODOS.md index 61d7934..4af0587 100644 --- a/TODOS.md +++ b/TODOS.md @@ -67,38 +67,38 @@ Note: These todos can wait until all the subcommands have been ported. ## Double-check AI output - [ ] cli.odin -- [ ] config.odin -- [ ] crypto.odin -- [ ] db.odin -- [ ] features.odin -- [ ] main.odin -- [ ] prompt.odin -- [ ] scan.odin -- [ ] sodium.odin -- [ ] ssh.odin -- [ ] table.odin +- [ ] cli_test.odin - [ ] cmd_backup.odin - [ ] cmd_check.odin +- [ ] cmd_check_test.odin - [ ] cmd_deps.odin - [ ] cmd_edit_config.odin - [ ] cmd_init.odin - [ ] cmd_list.odin +- [ ] cmd_list_test.odin - [ ] cmd_nushell_completion.odin +- [ ] cmd_nushell_completion_test.odin - [ ] cmd_remove.odin - [ ] cmd_restore.odin - [ ] cmd_scan.odin - [ ] cmd_sync.odin -- [ ] cmd_version.odin -- [ ] sqlite/sqlite.odin -- [ ] cli_test.odin -- [ ] cmd_check_test.odin -- [ ] cmd_list_test.odin -- [ ] cmd_nushell_completion_test.odin +- [x] cmd_version.odin +- [ ] config.odin - [ ] config_test.odin +- [ ] crypto.odin - [ ] crypto_test.odin +- [ ] db.odin - [ ] db_integration_test.odin - [ ] db_test.odin +- [ ] features.odin - [ ] features_test.odin +- [ ] main.odin +- [ ] prompt.odin +- [ ] scan.odin - [ ] scan_test.odin +- [ ] sodium.odin +- [ ] sqlite/sqlite.odin +- [ ] ssh.odin - [ ] ssh_test.odin +- [ ] table.odin - [ ] table_test.odin diff --git a/cmd_version.odin b/cmd_version.odin index 3bf94a1..b21b520 100644 --- a/cmd_version.odin +++ b/cmd_version.odin @@ -5,10 +5,6 @@ import "core:fmt" VERSION :: #load("version.txt", string) cmd_version :: proc(cmd: ^Command) { - if has_flag(cmd, "long") || has_flag(cmd, "l") { - fmt.printf("envr version %s\n", VERSION) - } else { - fmt.println(VERSION) - } + fmt.println(VERSION) }