Skip to content

Commit 7777ebc

Browse files
authored
test: CLI export end-to-end + manual QA checklist (closes #27) (#33)
* test: end-to-end coverage of scripts/export.py + manual QA checklist (closes #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`. * docs: tag CLI checklist code fences as bash + use default app port 3000 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. * review: Brad's PR #33 pass — XDG isolation, --no-composer fix, flag coverage 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 * test: derive XDG temp path via tempfile.gettempdir() (Ruff S108) 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.
1 parent 81a4010 commit 7777ebc

4 files changed

Lines changed: 421 additions & 2 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ instance/
2929
# Logs & local data
3030
*.log
3131
export/
32+
exports/
3233

3334
# IDE / OS
3435
.idea/

scripts/export.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ def resolve_workspace_path() -> str:
131131

132132

133133
def get_global_state_dir() -> str:
134+
# Honor XDG_STATE_HOME when set so the export state file (and manifest)
135+
# can be redirected — required for hermetic test runs and useful for
136+
# users following the XDG Base Directory spec. Falls back to the
137+
# historical ~/.cursor-chat-browser location when the env var is unset.
138+
xdg = os.environ.get("XDG_STATE_HOME")
139+
if xdg:
140+
return os.path.join(xdg, "cursor-chat-browser")
134141
return os.path.join(str(Path.home()), ".cursor-chat-browser")
135142

136143

@@ -488,8 +495,9 @@ def assign_workspace(cd, cid):
488495
exported = []
489496
count = 0
490497

491-
# Process IDE composers
492-
for row in ide_composer_rows:
498+
# Process IDE composers (skipped entirely when --no-composer was passed)
499+
include_composer = opts.get("include_composer", True)
500+
for row in ide_composer_rows if include_composer else []:
493501
composer_id = row["key"].split(":")[1]
494502
try:
495503
cd = json.loads(row["value"])

tests/cli-export-qa-checklist.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# CLI export + browse — manual QA checklist
2+
3+
Companion to the automated `tests/test_cli_export_e2e.py` suite. Run this once
4+
per release branch (or whenever `scripts/export.py`, `app.py`, or the
5+
workspace-scan service modules are touched) on each supported platform.
6+
7+
Mark each row pass/fail and attach short notes for any unexpected output.
8+
9+
## Environment
10+
11+
| Item | Value |
12+
|--------------------|------------------------------------------------|
13+
| OS / version | |
14+
| Python version | `python3 --version` |
15+
| Repo SHA | `git rev-parse HEAD` |
16+
| `WORKSPACE_PATH` | (env var, if overridden) |
17+
| Cursor data layout | default `~/.config/Cursor/User/workspaceStorage` or override |
18+
19+
## 1. CLI: `python scripts/export.py --help`
20+
21+
- [ ] Exits 0
22+
- [ ] Usage block lists `--since {all,last}`, `--out`, `--no-zip`, `--no-composer`, `--base-dir`, `--exclude-rules`
23+
- [ ] No stack trace, no deprecation warning
24+
25+
## 2. CLI: default zip export
26+
27+
```bash
28+
python scripts/export.py --out ./export
29+
```
30+
31+
- [ ] Exits 0
32+
- [ ] Final stdout line: `Exported N chat(s) to ./export/cursor-export-YYYY-MM-DD.zip`
33+
- [ ] Archive opens with `unzip -l` and contains at least one `.md` entry
34+
- [ ] Each `.md` inside the archive starts with a `---`-fenced YAML
35+
frontmatter containing `log_id`, `title`, `workspace`, `created_at`,
36+
`updated_at`
37+
38+
## 3. CLI: no-zip export
39+
40+
```bash
41+
python scripts/export.py --out ./export --no-zip
42+
```
43+
44+
- [ ] Exits 0
45+
- [ ] `./export/manifest.jsonl` exists and is non-empty
46+
- [ ] Each manifest line is valid JSON with `log_id`, `path`, `updated_at`
47+
- [ ] At least one `.md` file is written under `./export/<date>/...`
48+
- [ ] Frontmatter fields as in §2
49+
50+
## 4. CLI: incremental (`--since last`)
51+
52+
```bash
53+
python scripts/export.py --out ./export --no-zip --since last
54+
```
55+
56+
(run after §3)
57+
58+
- [ ] Exits 0
59+
- [ ] If no new chats: stdout prints `No conversations found since last export.`
60+
- [ ] Existing `.md` files in `./export` retain their previous mtimes (not rewritten)
61+
- [ ] After producing a new chat in Cursor, re-running picks it up and writes
62+
only the new file
63+
64+
## 5. App server launch
65+
66+
```bash
67+
python app.py
68+
```
69+
70+
- [ ] Process stays running for at least 30 s without crash
71+
- [ ] `curl -sI http://127.0.0.1:3000/` returns `HTTP/1.1 200 OK` (or `HTTP/1.0 200 OK` under some dev-server configurations)
72+
- [ ] Home page lists at least one workspace card (assuming real Cursor data)
73+
- [ ] No `Exception` / `Traceback` in server log
74+
75+
## 6. Browse flow (web UI)
76+
77+
Open `http://127.0.0.1:3000/` in a browser.
78+
79+
- [ ] Workspace list renders without console errors
80+
- [ ] Clicking a workspace card opens the workspace view
81+
- [ ] Within a workspace, opening a chat renders its bubbles
82+
- [ ] Markdown export button on a chat downloads a `.md` whose frontmatter
83+
matches §2
84+
- [ ] PDF export button on a chat downloads a `.pdf` that opens cleanly
85+
86+
## Sign-off
87+
88+
| Reviewer | Platform | Date | Result |
89+
|----------|----------|------|--------|
90+
| | | | |

0 commit comments

Comments
 (0)