test: HTTP/CLI route matrix and structured API error codes#42
test: HTTP/CLI route matrix and structured API error codes#42clean6378-max-it wants to merge 8 commits into
Conversation
Extract Flask client fixtures to tests/conftest.py, use client_single for search limit tests, and relax session count assertion to >= 1. Include utils/ in coverage (omit untested export modules), stop tracking .coverage, and align integration-tests CI with full cov scope. Co-Authored-By: Cursor <cursoragent@cursor.com>
Add client_empty and client_thinking fixtures, exercise thinking blocks via session detail API, preserve ValueError cause in _parse_limit, and remove .coverage from git tracking (already in .gitignore). Co-authored-by: Cursor <cursoragent@cursor.com>
|
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 (5)
📝 WalkthroughWalkthroughCentralizes API error handling with an ChangesStructured Error Codes and Comprehensive API/CLI Test Coverage
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
🚥 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)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
- Add ErrorCode enum and error_response() helper; migrate api/search, api/sessions, and api/export_api error paths to include stable "code" - Validate search limit query param (400 SEARCH_INVALID_LIMIT; cap at 500) - Add tests/conftest.py, fixtures, test_api_routes, test_cli_e2e, test_error_codes, test_search; extend test_export_api_bulk for codes - Document error code catalog in README
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
api/export_api.py (1)
287-317:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard
export_session()parse/stats failures with structured error responses.This path still has unhandled exceptions, so 500s here won’t consistently return
{ "error", "code" }.Proposed fix
- fmt = request.args.get("format", "md") - session = parse_session(filepath) + fmt = request.args.get("format", "md") + try: + session = parse_session(filepath) + except Exception: + current_app.logger.exception("Failed to parse session %s", session_id) + return error_response( + ErrorCode.PARSE_ERROR, + "Failed to parse session", + 500, + ) + rules = current_app.config.get("EXCLUSION_RULES") or [] if is_session_excluded(rules, session, project_name): return error_response( ErrorCode.SESSION_NOT_FOUND, "Session not found", 404, ) - stats = compute_stats(session) + try: + stats = compute_stats(session) + except Exception: + current_app.logger.exception("Failed to compute stats for %s", session_id) + return error_response( + ErrorCode.INTERNAL_ERROR, + "Failed to compute session stats", + 500, + ) title_slug = slugify(session["title"], default="session")🤖 Prompt for 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. In `@api/export_api.py` around lines 287 - 317, Wrap calls to parse_session(session filepath) and compute_stats(session) in try/except blocks inside export_session so any exceptions are caught and returned via the existing error_response(...) shape rather than bubbling as 500 HTMLs; specifically, catch exceptions from parse_session (and return a structured error_response with an appropriate ErrorCode like SESSION_NOT_FOUND or a new PARSE_ERROR and a 400/404) and catch exceptions from compute_stats (return ErrorCode.INTERNAL_ERROR or STATS_COMPUTE_FAILED with a 500), ensuring the rest of the flow (session_to_json/session_to_markdown and send_file) only runs when parsing and stats succeed.
🧹 Nitpick comments (4)
tests/test_api_routes.py (1)
68-72: ⚡ Quick winHarden capped-limit assertion with response-shape check.
This test can pass on non-list payloads (e.g., an unexpected JSON object) because it only checks
len(...) <= 500. Assert list shape explicitly before length checks.Proposed test hardening
def test_search_limit_capped_at_max(client): resp = client.get("/api/search?q=Hello&limit=9999") assert resp.status_code == 200 - assert len(resp.get_json()) <= 500 + body = resp.get_json() + assert isinstance(body, list) + assert len(body) <= 500🤖 Prompt for 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. In `@tests/test_api_routes.py` around lines 68 - 72, The test_search_limit_capped_at_max test currently only checks len(resp.get_json()) <= 500 which will pass for non-list JSON shapes; update the test to first assert the response body is a JSON list (e.g., use resp.is_json and assert isinstance(resp.get_json(), list) or equivalent) before asserting its length, so the shape is validated prior to the capped-limit check in test_search_limit_capped_at_max.tests/test_search.py (1)
8-12: ⚡ Quick winTighten happy-path assertions to validate limit behavior.
These tests currently confirm status, but not enough of the response contract. Add explicit list-shape checks and verify requested/default limit constraints in assertions.
Proposed assertion upgrades
def test_limit_integer_string(client_single): resp = client_single.get("/api/search?q=Hello&limit=10") assert resp.status_code == 200 - assert isinstance(resp.get_json(), list) + body = resp.get_json() + assert isinstance(body, list) + assert len(body) <= 10 @@ def test_limit_default(client_single): resp = client_single.get("/api/search?q=Hello") assert resp.status_code == 200 + body = resp.get_json() + assert isinstance(body, list) + assert len(body) <= 200Also applies to: 26-29
🤖 Prompt for 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. In `@tests/test_search.py` around lines 8 - 12, Update the happy-path assertions in test_limit_integer_string (and the similar test at lines 26-29) to assert the response is a list, each element has the expected shape (e.g., dict with expected keys) and that the number of returned items respects the requested or default limit: assert isinstance(resp.get_json(), list), assert all(isinstance(item, dict) and set(expected_keys).issubset(item.keys()) for item in resp.get_json()), and assert len(resp.get_json()) <= requested_limit (for "/api/search?q=Hello&limit=10" assert <= 10) and also add a case that verifies the default limit when limit is omitted by calling the endpoint without limit and asserting len(...) <= default_limit; use the test function names test_limit_integer_string and the other test to locate and update the assertions.tests/test_cli_e2e.py (2)
50-55: ⚡ Quick winTighten success assertion for listed project.
On Line 54,
or "Project" in proc.stdoutcan pass even whentest-projectis missing (header-only output). Assert the seeded project name directly to avoid false positives.Proposed diff
def test_cli_list_exits_zero(tmp_path): base = _seed_base_dir(tmp_path) proc = _run_cli(["list", "--base-dir", str(base)]) assert proc.returncode == 0 - assert "test-project" in proc.stdout or "Project" in proc.stdout + assert "test-project" in proc.stdout🤖 Prompt for 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. In `@tests/test_cli_e2e.py` around lines 50 - 55, The test test_cli_list_exits_zero uses a loose assertion that allows header-only output to pass; tighten it by removing the fallback check ("or 'Project' in proc.stdout") and assert directly that the seeded project name "test-project" appears in proc.stdout (use the existing proc from _run_cli in test_cli_list_exits_zero to perform the check).
70-73: ⚡ Quick winAssert the invalid
--sincefailure reason, not only exit code.Line 72 currently accepts any non-zero failure. Add a stderr assertion so the test proves the
--sincevalidation path specifically failed.Proposed diff
def test_cli_invalid_since_exits_nonzero(): proc = _run_cli(["--since", "yesterday"]) assert proc.returncode != 0 + assert "--since" in proc.stderr or "invalid choice" in proc.stderr.lower()🤖 Prompt for 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. In `@tests/test_cli_e2e.py` around lines 70 - 73, The test test_cli_invalid_since_exits_nonzero only checks a non-zero exit code; update it to also assert that _run_cli([...])'s stderr contains the specific validation error for the --since flag (e.g., assert "Invalid value for '--since'" in proc.stderr or a substring like "--since" and "invalid" if exact text differs) so the test verifies the --since validation path failed; keep using the existing helper _run_cli to capture stdout/stderr and add the stderr assertion alongside the returncode check.
🤖 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 `@api/sessions.py`:
- Around line 65-75: The current single try around parse_session(filepath) and
compute_stats(session) masks parse failures as INTERNAL_ERROR; split the logic
so parse errors are caught and mapped to ErrorCode.PARSE_ERROR. Specifically,
call parse_session(filepath) in its own try/except that catches parsing
exceptions and returns error_response(ErrorCode.PARSE_ERROR, "Failed to parse
session", 400) (include session_id in logs via current_app.logger.exception),
then call compute_stats(session) in a separate try/except that logs and returns
the existing INTERNAL_ERROR via error_response(ErrorCode.INTERNAL_ERROR, "Failed
to compute session stats", 500) if compute_stats fails. Ensure you reference
parse_session, compute_stats, session_id, current_app.logger.exception,
error_response and ErrorCode.PARSE_ERROR in the change.
In `@tests/conftest.py`:
- Around line 22-24: The shared error helper in tests/conftest.py currently only
asserts "code" is present in body; add a type assertion to ensure body["code"]
is a string (e.g., use isinstance(body["code"], str)) before comparing with
expected_code in the helper that contains the assertions around "code" and
expected_code so tests fail if handlers return non-string codes.
---
Outside diff comments:
In `@api/export_api.py`:
- Around line 287-317: Wrap calls to parse_session(session filepath) and
compute_stats(session) in try/except blocks inside export_session so any
exceptions are caught and returned via the existing error_response(...) shape
rather than bubbling as 500 HTMLs; specifically, catch exceptions from
parse_session (and return a structured error_response with an appropriate
ErrorCode like SESSION_NOT_FOUND or a new PARSE_ERROR and a 400/404) and catch
exceptions from compute_stats (return ErrorCode.INTERNAL_ERROR or
STATS_COMPUTE_FAILED with a 500), ensuring the rest of the flow
(session_to_json/session_to_markdown and send_file) only runs when parsing and
stats succeed.
---
Nitpick comments:
In `@tests/test_api_routes.py`:
- Around line 68-72: The test_search_limit_capped_at_max test currently only
checks len(resp.get_json()) <= 500 which will pass for non-list JSON shapes;
update the test to first assert the response body is a JSON list (e.g., use
resp.is_json and assert isinstance(resp.get_json(), list) or equivalent) before
asserting its length, so the shape is validated prior to the capped-limit check
in test_search_limit_capped_at_max.
In `@tests/test_cli_e2e.py`:
- Around line 50-55: The test test_cli_list_exits_zero uses a loose assertion
that allows header-only output to pass; tighten it by removing the fallback
check ("or 'Project' in proc.stdout") and assert directly that the seeded
project name "test-project" appears in proc.stdout (use the existing proc from
_run_cli in test_cli_list_exits_zero to perform the check).
- Around line 70-73: The test test_cli_invalid_since_exits_nonzero only checks a
non-zero exit code; update it to also assert that _run_cli([...])'s stderr
contains the specific validation error for the --since flag (e.g., assert
"Invalid value for '--since'" in proc.stderr or a substring like "--since" and
"invalid" if exact text differs) so the test verifies the --since validation
path failed; keep using the existing helper _run_cli to capture stdout/stderr
and add the stderr assertion alongside the returncode check.
In `@tests/test_search.py`:
- Around line 8-12: Update the happy-path assertions in
test_limit_integer_string (and the similar test at lines 26-29) to assert the
response is a list, each element has the expected shape (e.g., dict with
expected keys) and that the number of returned items respects the requested or
default limit: assert isinstance(resp.get_json(), list), assert
all(isinstance(item, dict) and set(expected_keys).issubset(item.keys()) for item
in resp.get_json()), and assert len(resp.get_json()) <= requested_limit (for
"/api/search?q=Hello&limit=10" assert <= 10) and also add a case that verifies
the default limit when limit is omitted by calling the endpoint without limit
and asserting len(...) <= default_limit; use the test function names
test_limit_integer_string and the other test to locate and update the
assertions.
🪄 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: 93da7cea-ce58-4a41-9562-246a7f450f6a
📒 Files selected for processing (13)
README.mdapi/error_codes.pyapi/export_api.pyapi/search.pyapi/sessions.pytests/conftest.pytests/fixtures/session_minimal.jsonltests/fixtures/session_with_tools.jsonltests/test_api_routes.pytests/test_cli_e2e.pytests/test_error_codes.pytests/test_export_api_bulk.pytests/test_search.py
20ac332 to
b2f1331
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
api/sessions.py (1)
65-75:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSplit parse and compute error handling in stats endpoint.
At Line 66 and Line 67, parse and stats computation share one
except, so parse failures are still returned asINTERNAL_ERRORinstead ofPARSE_ERROR.Suggested fix
- try: - session = parse_session(filepath) - stats = compute_stats(session) - return jsonify(stats) - except Exception: - current_app.logger.exception("Failed to compute stats for %s", session_id) - return error_response( - ErrorCode.INTERNAL_ERROR, - "Failed to compute session stats", - 500, - ) + try: + session = parse_session(filepath) + except Exception: + current_app.logger.exception("Failed to parse session %s", session_id) + return error_response( + ErrorCode.PARSE_ERROR, + "Failed to parse session", + 500, + ) + + try: + stats = compute_stats(session) + return jsonify(stats) + except Exception: + current_app.logger.exception("Failed to compute stats for %s", session_id) + return error_response( + ErrorCode.INTERNAL_ERROR, + "Failed to compute session stats", + 500, + )🤖 Prompt for 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. In `@api/sessions.py` around lines 65 - 75, The endpoint currently wraps parse_session(filepath) and compute_stats(session) in a single try/except so parse failures are treated as INTERNAL_ERROR; split them into two blocks: call parse_session(filepath) inside its own try/except and on parse failure log via current_app.logger.exception("Failed to parse session %s", session_id) and return error_response(ErrorCode.PARSE_ERROR, "Failed to parse session", 400) (catch the specific parse exception class if available, e.g., ParseError or ValueError), then call compute_stats(session) in a separate try/except that logs via current_app.logger.exception("Failed to compute stats for %s", session_id) and returns the existing INTERNAL_ERROR response on other failures.
🤖 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 `@api/export_api.py`:
- Around line 286-295: Wrap the calls to parse_session(filepath) and
compute_stats(session) in try/except blocks so parsing or stats errors return
structured error_response objects (use the existing error_response and ErrorCode
enums) instead of raising uncaught exceptions; specifically, catch exceptions
from parse_session in the block where parse_session is invoked (before
is_session_excluded) and return a clear error_response (e.g.,
ErrorCode.SESSION_NOT_FOUND or a validation error) with a short message
including the exception text and an appropriate HTTP status, and catch
exceptions from compute_stats(session) where compute_stats is called and return
error_response(ErrorCode.INTERNAL_ERROR, "Failed to compute stats: <error>",
500) so all failures yield stable responses containing the expected code field.
In `@static/js/shared/state.test.js`:
- Around line 5-10: The test suite currently only resets the shared singleton in
an afterEach block, which leaves the first test vulnerable to prior mutations;
add a beforeEach that performs the same resets as the existing afterEach (reset
state.currentProject, state.cachedSessions, state.projectDisplayNames,
state.navInProgress) so each test starts with a deterministic state; locate the
existing afterEach in the file (and the shared state object reference named
state) and duplicate its reset logic inside a beforeEach at the top of the
suite.
In `@tests/test_cli_e2e.py`:
- Around line 70-73: The test test_cli_invalid_since_exits_nonzero is
under-specified because it calls _run_cli(["--since", "yesterday"]) without a
subcommand so a non-zero exit could be from argument shape errors rather than
--since validation; update the test to invoke the CLI with a real subcommand
(use whatever existing subcommand your CLI expects when exercising --since) via
_run_cli, assert proc.returncode != 0, and also assert proc.stderr contains the
specific validation error text related to --since (or a known error fragment) to
ensure the failure is the intended validation path.
---
Duplicate comments:
In `@api/sessions.py`:
- Around line 65-75: The endpoint currently wraps parse_session(filepath) and
compute_stats(session) in a single try/except so parse failures are treated as
INTERNAL_ERROR; split them into two blocks: call parse_session(filepath) inside
its own try/except and on parse failure log via
current_app.logger.exception("Failed to parse session %s", session_id) and
return error_response(ErrorCode.PARSE_ERROR, "Failed to parse session", 400)
(catch the specific parse exception class if available, e.g., ParseError or
ValueError), then call compute_stats(session) in a separate try/except that logs
via current_app.logger.exception("Failed to compute stats for %s", session_id)
and returns the existing INTERNAL_ERROR response on other failures.
🪄 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: b3934581-cfaf-4dc7-841d-4cc84fc0bd73
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (24)
.github/workflows/ci.yml.gitignoreREADME.mdapi/error_codes.pyapi/export_api.pyapi/search.pyapi/sessions.pypackage.jsonpyproject.tomlrequirements-dev.txtstatic/js/shared/markdown.test.jsstatic/js/shared/state.test.jsstatic/js/shared/utils.test.jstests/conftest.pytests/fixtures/session_minimal.jsonltests/fixtures/session_with_thinking.jsonltests/fixtures/session_with_tools.jsonltests/test_api_integration.pytests/test_api_routes.pytests/test_cli_e2e.pytests/test_error_codes.pytests/test_export_api_bulk.pytests/test_search.pyvitest.config.js
✅ Files skipped from review due to trivial changes (4)
- package.json
- pyproject.toml
- .gitignore
- README.md
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 `@api/sessions.py`:
- Around line 67-73: The except block that catches parser exceptions (in the
session handling function around current_app.logger.exception("Failed to parse
session %s", session_id)) returns ErrorCode.PARSE_ERROR with HTTP 400; change
the response status to 500 to match get_session's PARSE_ERROR handling and
maintain a consistent server-error contract. Update the error_response call in
that except handler to use a 500 status, and apply the same change to the
mirrored export-session parse handling so both endpoints return PARSE_ERROR as a
500 server error.
🪄 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: 8da4a479-8f9a-42d1-9a60-9ef18f6f6cce
📒 Files selected for processing (7)
api/export_api.pyapi/sessions.pytests/conftest.pytests/test_api_routes.pytests/test_cli_e2e.pytests/test_error_propagation.pytests/test_search.py
| status: int, | ||
| **extra: object, | ||
| ) -> tuple[Response, int]: | ||
| body: dict[str, object] = {"error": message, "code": str(code)} |
There was a problem hiding this comment.
ErrorCode is StrEnum, so code is already a str subclass. str(code) works but code.value or just code is more idiomatic — both yield "SEARCH_INVALID_LIMIT" directly. Same nit applies to expected_code=str(code) in tests/test_error_codes.py (in the parametrized assertion at the bottom of the file). Optional.
|
|
||
|
|
||
| @pytest.fixture | ||
| def client(tmp_path, export_state_file): |
There was a problem hiding this comment.
Four near-identical client fixtures (client, client_single, client_empty, client_thinking) repeat the same mkdir / copy / create_app / TESTING boilerplate. Same DRY concern I flagged on PR #40 — still not addressed. Worth collapsing to a parametric helper that takes a list of (fixture_file, output_name) tuples and returns a client. Recommend: a _make_client(tmp_path, export_state_file, seeds: list[tuple[str, str]]) private helper + 4 thin fixture wrappers around it.
| globalThis.marked = { | ||
| parse: vi.fn((text) => `<p>${text}</p>`), | ||
| }; | ||
| globalThis.DOMPurify = { |
There was a problem hiding this comment.
The mock replaces DOMPurify.sanitize with a regex that strips <script>, then the test asserts the output doesn't contain <script>. Same circular-mock issue I flagged on PR #40 — still not addressed.
| 404, | ||
| ) | ||
| return jsonify(session) | ||
| except Exception: |
There was a problem hiding this comment.
Three except Exception: blocks catch too broadly. Same narrowing recommendation as before — (json.JSONDecodeError, KeyError, ValueError, OSError, FileNotFoundError).
Same as line 67, 78.
| ) | ||
| new_sessions_map[sid] = sess_info.get("modified", 0) | ||
| count += 1 | ||
| except Exception as e: |
There was a problem hiding this comment.
Four broad except Exception: sites — the two inside the export loops (162, 224) continue silently and the two below (289, 309) wrap parse/stats failures.
Same as 224, 289, 309
| if count > 0: | ||
| _write_state(new_sessions_map, count) | ||
|
|
||
| if count == 0: | ||
| return ( | ||
| jsonify( | ||
| { | ||
| "error": "Nothing to export", | ||
| "since": since, | ||
| } | ||
| ), | ||
| return error_response( | ||
| ErrorCode.EXPORT_NOTHING_TO_EXPORT, | ||
| "Nothing to export", |
There was a problem hiding this comment.
Reordering to if count == 0: return ... first (then write state for the count > 0 path) reads more linearly. Cosmetic.
| def test_cli_list_unknown_project_exits_zero_with_message(tmp_path): | ||
| base = _seed_base_dir(tmp_path) | ||
| proc = _run_cli(["list", "--base-dir", str(base), "--project", "does-not-exist"]) | ||
| assert proc.returncode == 0 | ||
| assert "No projects found" in proc.stdout |
There was a problem hiding this comment.
Test name says "unknown project" but assertion checks for "No projects found" (about no projects at all). If --project does-not-exist is supposed to show a project-specific "no such project: does-not-exist" message, the test would currently pass even on the wrong message
| omit = [ | ||
| "tests/*", | ||
| "utils/md_exporter.py", | ||
| "utils/session_stats.py", | ||
| "utils/json_exporter.py", | ||
| ] |
There was a problem hiding this comment.
The three utils/ modules omitted from coverage are domain logic, not glue — session_stats.py produces the cost_estimate_usd shown in stats endpoints, md_exporter.py is used by every bulk-export test in this PR, and json_exporter.py likewise.
Closes #41
Summary
Week 3 Wednesday work for
claude-code-chat-browser(8 story points):test_api_routes.py), shared fixtures (tests/conftest.py), CLI subprocess e2e (test_cli_e2e.py), and search limit unit tests (test_search.py).ErrorCodeenum +error_response()helper; every API JSON error includes"code"and"error"; README error catalog.Canonical issues:
fab633e4-4f9d-56b4-97d4-cfc84b5bbdd7(HTTP/CLI tests) ·8c01c675-d0e7-5e5c-a179-472a2fea5056(structured error codes)Detail guide:
Doc/Issues/chen-week3-wednesday-claude-code-chat-browser-guide.mdChanges
API
api/error_codes.pyErrorCode(StrEnum) anderror_response()api/search.py_parse_limit(); 400SEARCH_INVALID_LIMIT; max limit 500api/sessions.pyINVALID_PATH,SESSION_NOT_FOUND,PARSE_ERROR,INTERNAL_ERROR)api/export_api.pyTests
tests/conftest.pyclientfixtures,export_state_file,assert_error_response()tests/fixtures/*.jsonltests/test_api_routes.pytests/test_cli_e2e.pylist,stats,export, invalid--since(UTF-8 env for Windows)tests/test_error_codes.py"code"field assertionstests/test_search.pySEARCH_INVALID_LIMITtests/test_export_api_bulk.py"code"on isolated blueprint testsDocs
README.md— API error code table; bulk export 422 example includes"code"Example error response
{ "error": "Invalid limit: must be a positive integer", "code": "SEARCH_INVALID_LIMIT" } <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * API error responses now include stable, machine-readable error codes alongside human messages. * **Documentation** * Bulk Export docs expanded; new API error codes reference and standardized JSON error shape documented. * **Bug Fixes** * Consistent validation and structured errors across search, export, and session endpoints (including 422 when bulk export yields nothing and echoing the requested mode). * **Tests** * Added broad unit, integration, and end-to-end tests covering API, CLI, and frontend utilities. * **Chores** * CI and JS test tooling configuration updates. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/cppalliance/claude-code-chat-browser/pull/42?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->