Skip to content

test: CLI export end-to-end + manual QA checklist (closes #27)#33

Open
timon0305 wants to merge 4 commits into
masterfrom
feat/cli-export-e2e-27
Open

test: CLI export end-to-end + manual QA checklist (closes #27)#33
timon0305 wants to merge 4 commits into
masterfrom
feat/cli-export-e2e-27

Conversation

@timon0305
Copy link
Copy Markdown
Collaborator

@timon0305 timon0305 commented May 13, 2026

Closes #27.

Summary

  • 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. 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's conftest.py being merged first; the suite runs on master CI as-is under unittest discover.
  • tests/cli-export-qa-checklist.md — manual companion checklist covering --help, zip + no-zip exports, --since last idempotency, python app.py --port 3001 launch + curl smoke, and the browse-flow markdown/PDF downloads.
  • .gitignore — adds exports/ next to the existing export/ entry so either pluralisation of the example output directory stays untracked.

Automated coverage

Test Asserts
test_export_no_zip_writes_markdown_files --no-zip produces .md files; seeded composer id appears in a filename
test_markdown_frontmatter_has_required_fields frontmatter contains log_id, title, workspace, created_at, updated_at
test_export_zip_mode_writes_archive default mode writes cursor-export-<date>.zip containing .md entries
test_manifest_jsonl_has_expected_shape manifest.jsonl entries carry log_id, path, updated_at
test_since_last_skips_already_exported_records second --since last run leaves existing files' mtimes untouched

The --since last test wraps the inner main() call in a SystemExit guard because, on a clean second pass, the script prints No 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 locally
  • python3 -m unittest discover tests — 183/183 pass, no regression
  • python3 app.py --port 3001 boots without traceback; curl -sI http://127.0.0.1:3001/ returns HTTP/1.1 200 OK
  • CI green (matrix: ubuntu-latest × Python 3.11 / 3.12 / 3.13)
  • Manual QA per tests/cli-export-qa-checklist.md on a real Cursor profile

Summary by CodeRabbit

  • New Features

    • CLI: support for using the XDG state directory and an option to skip IDE composer exports (--no-composer).
  • Tests

    • Added end-to-end tests validating CLI export: markdown/zip output, manifest creation, incremental exports, and exclusion rules.
  • Documentation

    • Added a QA checklist for CLI export and web UI verification steps.
  • Chores

    • Updated ignore rules to exclude generated export artifacts.

Review Change Stack

…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`.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8aa998ec-c581-4132-8e00-5dce9151810a

📥 Commits

Reviewing files that changed from the base of the PR and between c1ae9d1 and 6d1a572.

📒 Files selected for processing (1)
  • tests/test_cli_export_e2e.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_cli_export_e2e.py

📝 Walkthrough

Walkthrough

Adds 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 .gitignore entry to ignore exports/.

Changes

CLI Export Testing Suite

Layer / File(s) Summary
Export script: XDG state dir & composer gating
scripts/export.py
get_global_state_dir() honors XDG_STATE_HOME and export skips IDE composer rows when --no-composer is passed.
Test infrastructure and fixtures
tests/test_cli_export_e2e.py
Module scaffolding with seeded IDs, SQLite/filesystem fixture builders for workspace/global state, _run_export() context manager, and test class setup/teardown.
Export behavior validation tests
tests/test_cli_export_e2e.py
Tests for --no-zip markdown generation and YAML frontmatter, default zip archive contents, manifest.jsonl shape, incremental --since last preserving mtimes, --no-composer skipping IDE composer data, and --exclude-rules filtering; includes unittest entrypoint.
Build configuration and QA documentation
.gitignore, tests/cli-export-qa-checklist.md
.gitignore updated to exclude exports/; manual QA checklist documents CLI help, zip vs no-zip checks, incremental export expectations, server launch/health, and web UI export verification.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Suggested reviewers

  • bradjin8
  • wpak-ai

Poem

🐇 I hopped through tests with tidy paws,
Zips and manifests met the laws,
Markdown frontmatter neat and bright,
Incremental runs kept timestamps right,
A carrot for QA, cheers tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: adding end-to-end tests and a manual QA checklist for CLI export functionality, and references the related issue.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-export-e2e-27

Comment @coderabbitai help to get the list of available commands and usage tips.

@timon0305 timon0305 self-assigned this May 13, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 95d3140 and a51109d.

📒 Files selected for processing (3)
  • .gitignore
  • tests/cli-export-qa-checklist.md
  • tests/test_cli_export_e2e.py

Comment thread tests/cli-export-qa-checklist.md Outdated
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.
@timon0305 timon0305 requested a review from bradjin8 May 13, 2026 14:13
Copy link
Copy Markdown
Collaborator

@bradjin8 bradjin8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread tests/test_cli_export_e2e.py
Comment thread tests/test_cli_export_e2e.py
Comment thread tests/cli-export-qa-checklist.md Outdated
…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
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between fea609d and c1ae9d1.

📒 Files selected for processing (3)
  • scripts/export.py
  • tests/cli-export-qa-checklist.md
  • tests/test_cli_export_e2e.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/cli-export-qa-checklist.md

Comment thread tests/test_cli_export_e2e.py Outdated
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.
@timon0305 timon0305 requested a review from bradjin8 May 13, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI testing: scripted QA + manual verification of export/browse commands

2 participants