test: CLI export end-to-end + manual QA checklist (closes #27)#33
test: CLI export end-to-end + manual QA checklist (closes #27)#33timon0305 wants to merge 4 commits into
Conversation
…loses #27) Adds tests/test_cli_export_e2e.py — five unittest cases that drive scripts/export.py main() against a self-contained SQLite fixture (workspaceStorage + globalStorage cursorDiskKV) under tempfile, with WORKSPACE_PATH / CLI_CHATS_PATH / XDG_STATE_HOME overrides. Coverage: - no-zip mode writes .md files under <out>/<date>/... - YAML frontmatter contains log_id, title, workspace, created_at, updated_at - default zip mode writes cursor-export-<date>.zip containing .md entries - manifest.jsonl entries carry log_id / path / updated_at - --since last is idempotent — second run does not rewrite unchanged files Adds tests/cli-export-qa-checklist.md as the manual companion: --help, zip + no-zip + --since last exports, app.py launch + curl smoke, markdown/PDF download from the web UI. Adds exports/ to .gitignore alongside the existing export/ entry so either pluralisation of the example output directory stays untracked. The fixture is self-contained (does not depend on the conftest.py from PR #32) so the suite runs on master CI as-is under `unittest discover`.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds XDG_STATE_HOME support and a composer-export toggle to the CLI export script, a comprehensive end-to-end unittest suite with deterministic fixtures for export behaviors (markdown/zip/manifest/incremental/exclude), a manual QA checklist, and ChangesCLI Export Testing Suite
Sequence Diagram(s)sequenceDiagram
participant Tester as CLI
participant Env as XDG_STATE_HOME
participant ExportScript as scripts.export.main
participant StateDir as cursor-chat-browser state dir
participant FS as filesystem (workspace, output)
Tester->>Env: set XDG_STATE_HOME / env vars
Tester->>ExportScript: run export (argv)
ExportScript->>Env: call get_global_state_dir()
Env-->>StateDir: provide ${XDG_STATE_HOME}/cursor-chat-browser
ExportScript->>StateDir: read/write manifest.jsonl and state DB
ExportScript->>FS: write .md files / zip archive
ExportScript->>FS: skip ide_composer_rows if include_composer is false
ExportScript-->>Tester: stdout + exit
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/cli-export-qa-checklist.md`:
- Around line 27-29: Add explicit fenced-code language identifiers (bash) to the
command snippets so markdownlint MD040 is satisfied: update each triple-backtick
block containing the commands "python scripts/export.py --out ./export", "python
scripts/export.py --out ./export --no-zip", "python scripts/export.py --out
./export --no-zip --since last", and "python app.py --port 3001" to start with
```bash (and apply the same change to the other similar blocks noted at lines
40-42, 52-54, 66-68) so the code fences include the language token.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e3dbad24-43e8-427a-a400-ffb9a1777744
📒 Files selected for processing (3)
.gitignoretests/cli-export-qa-checklist.mdtests/test_cli_export_e2e.py
CodeRabbit MD040 — fenced blocks need a language identifier. Tagged the four command snippets as `bash`. Dropped the `--port 3001` override from §5 / §6 in favour of the script's default port 3000; the explicit flag was a holdover from issue #27's wording with no actual reason to deviate from the default.
bradjin8
left a comment
There was a problem hiding this comment.
tests/test_cli_export_e2e.py, line 97 and scripts/export.py, lines 131–133: --since last test is not hermetically isolated
_run_export sets XDG_STATE_HOME to a temp directory with the clear intent of redirecting where export_state.json is saved and read. But scripts/export.py's get_global_state_dir() hardcodes ~/.cursor-chat-browser and never reads XDG_STATE_HOME. The state file is therefore always written to the real user's home directory.
The --since last test still passes on a clean machine because the sequence is:
First run (no --since) writes ~/.cursor-chat-browser/export_state.json with lastExportTime = now (2026).
Second run (--since last) reads it back, finds lastExportTime (2026) > fixture.lastUpdatedAt (2024), skips the composer, exits 0.
SystemExit(0) is caught; mtime check passes since files weren't rewritten.
The test passes by coincidence of the timestamp gap, not by design. The side effects are real:
On any developer machine that runs the test suite, ~/.cursor-chat-browser/export_state.json gets overwritten with lastExportTime = now. Their next scripts/export.py --since last run will skip all conversations with timestamps before the test ran.
tearDown only cleans up self._tmp; ~/.cursor-chat-browser is never restored.
Fix: make get_global_state_dir() in scripts/export.py honor XDG_STATE_HOME when set (standard XDG convention). Alternatively, mock get_global_state_dir in the test with unittest.mock.patch.
…overage Six follow-ups across two threads of Brad's review. Production fixes: * scripts/export.py get_global_state_dir() now honors XDG_STATE_HOME (returns $XDG_STATE_HOME/cursor-chat-browser), falling back to the historical ~/.cursor-chat-browser only when the env var is unset. Without this, the --since last test leaked state into the developer's real home directory; the test still passed but only by timestamp coincidence and corrupted ~/.cursor-chat-browser/export_state.json on every CI run. * scripts/export.py --no-composer was a parsed-but-unused flag — opts["include_composer"] was set but never consulted. Added the missing guard at the IDE-composer iteration site so the flag now actually skips composer rows. Test additions to tests/test_cli_export_e2e.py: * Both DB-seeding helpers (_make_global_state_db, _make_workspace_storage) now use contextlib.closing(sqlite3.connect(...)). A mid-setup exception used to leak the handle and on Windows that blocks TemporaryDirectory.cleanup(). * New TestGetGlobalStateDir regression class — two tests pinning both branches of the XDG behavior so this can't silently regress. * test_no_composer_skips_ide_composer_data — fixture seeds composer data exclusively, so --no-composer must produce zero markdown. * test_exclude_rules_filters_matching_composer — writes a rules file containing "E2E" (substring of the seeded composer's title) and asserts the seeded composer is filtered out before any markdown is written. Doc fix in tests/cli-export-qa-checklist.md: * `curl -sI` sends HTTP/1.1 by default and the dev server replies in kind. Listing HTTP/1.0 first would have triggered false-fail reports from testers seeing HTTP/1.1. Verified locally: - python3 -m unittest tests.test_cli_export_e2e: 9 passed - python3 -m unittest discover tests: 187 passed, OK - ~/.cursor-chat-browser never created during the run - python3 app.py boots clean; curl -sI returns HTTP/1.1 200 OK
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_cli_export_e2e.py`:
- Around line 137-141: Replace the hardcoded "/tmp/..." expectation with a
platform-agnostic temp root: use tempfile.gettempdir() to derive the temp root,
set os.environ["XDG_STATE_HOME"] = os.path.join(temp_root, "some-xdg-root"), and
assert export_script.get_global_state_dir() == os.path.join(temp_root,
"some-xdg-root", "cursor-chat-browser"); update the test in
tests/test_cli_export_e2e.py accordingly to avoid the S108 lint error and ensure
cross-platform correctness.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 10808dd4-a779-464e-9d03-a2e2067ad6a2
📒 Files selected for processing (3)
scripts/export.pytests/cli-export-qa-checklist.mdtests/test_cli_export_e2e.py
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/cli-export-qa-checklist.md
CodeRabbit flagged the hardcoded `/tmp/some-xdg-root` literal in test_uses_xdg_state_home_when_set: it's Linux-specific and trips Ruff's S108 (hardcoded-tmp-directory) lint. Derive the path from tempfile.gettempdir() and join with os.path.join so the test runs on Windows / macOS too and stays lint-clean.
Closes #27.
Summary
tests/test_cli_export_e2e.py— five unittest cases that drivescripts/export.pymain()against a self-contained SQLite fixture (workspaceStorage + globalStoragecursorDiskKV) undertempfile, withWORKSPACE_PATH/CLI_CHATS_PATH/XDG_STATE_HOMEoverrides. The fixture is local to the file so the branch does not depend on PR feat(tests): pytest endpoint coverage via Flask test client (closes #26) #32'sconftest.pybeing merged first; the suite runs on master CI as-is underunittest discover.tests/cli-export-qa-checklist.md— manual companion checklist covering--help, zip + no-zip exports,--since lastidempotency,python app.py --port 3001launch +curlsmoke, and the browse-flow markdown/PDF downloads..gitignore— addsexports/next to the existingexport/entry so either pluralisation of the example output directory stays untracked.Automated coverage
test_export_no_zip_writes_markdown_files--no-zipproduces.mdfiles; seeded composer id appears in a filenametest_markdown_frontmatter_has_required_fieldslog_id,title,workspace,created_at,updated_attest_export_zip_mode_writes_archivecursor-export-<date>.zipcontaining.mdentriestest_manifest_jsonl_has_expected_shapemanifest.jsonlentries carrylog_id,path,updated_attest_since_last_skips_already_exported_records--since lastrun leaves existing files' mtimes untouchedThe
--since lasttest wraps the innermain()call in aSystemExitguard because, on a clean second pass, the script printsNo conversations found since last export.and exits 0 — the no-rewrite contract is still verifiable through the unchanged file mtimes.Test plan
python3 -m unittest tests.test_cli_export_e2e -v— 5/5 pass locallypython3 -m unittest discover tests— 183/183 pass, no regressionpython3 app.py --port 3001boots without traceback;curl -sI http://127.0.0.1:3001/returnsHTTP/1.1 200 OKtests/cli-export-qa-checklist.mdon a real Cursor profileSummary by CodeRabbit
New Features
Tests
Documentation
Chores