diff --git a/.superpowers/sdd/task-2-report.md b/.superpowers/sdd/task-2-report.md index 62dd7afa..404a87ab 100644 --- a/.superpowers/sdd/task-2-report.md +++ b/.superpowers/sdd/task-2-report.md @@ -149,3 +149,29 @@ session while the adapter executes as `thoth_sessions_runtime`. - Task 7 must safely provision the dedicated runtime login/membership and inject its TLS credentials/CA; this task intentionally does not create a deployment-specific LOGIN role or password. + +## Review follow-up — unavailable migration database JSON contract + +### RED + +Command: + +```sh +cd harness && .venv/bin/pytest \ + tests/test_session_migrate_cmd.py::test_session_migrate_status_database_failure_is_pristine_json -q +``` + +Result: `1 failed in 0.46s`. The unreachable direct PostgreSQL URL exited with code 1 but left +stdout empty, so `json.loads(result.stdout)` raised `JSONDecodeError`. + +### GREEN + +The session migration CLI now catches `SQLAlchemyError` at the same command boundary as its +migration/domain errors and emits only `{"error": ...}` on stdout for `--json`. + +```sh +cd harness && .venv/bin/pytest tests/test_session_migrate_cmd.py -q +cd harness && .venv/bin/ruff check tht/cli/session_cmd.py tests/test_session_migrate_cmd.py +``` + +Result: `2 passed in 3.60s`; Ruff: `All checks passed!`. diff --git a/harness/tests/test_session_migrate_cmd.py b/harness/tests/test_session_migrate_cmd.py index e784a380..66cb8172 100644 --- a/harness/tests/test_session_migrate_cmd.py +++ b/harness/tests/test_session_migrate_cmd.py @@ -26,3 +26,22 @@ def test_session_migrate_status_is_pristine_and_idempotent(): assert second.exit_code == 0, second.output assert json.loads(first.stdout) == expected assert json.loads(second.stdout) == expected + + +def test_session_migrate_status_database_failure_is_pristine_json(): + result = CliRunner().invoke( + app, + [ + "session", + "migrate", + "--database-url", + "postgresql+psycopg2://test:test@127.0.0.1:1/test", + "--status", + "--json", + ], + ) + + assert result.exit_code == 1 + assert result.stderr == "" + assert "Traceback" not in result.stdout + assert json.loads(result.stdout)["error"] diff --git a/harness/tht/cli/session_cmd.py b/harness/tht/cli/session_cmd.py index 86f10ff5..05cd0814 100644 --- a/harness/tht/cli/session_cmd.py +++ b/harness/tht/cli/session_cmd.py @@ -2,6 +2,7 @@ import json from pathlib import Path import typer +from sqlalchemy.exc import SQLAlchemyError from tht.cli.config_cmd import CONFIG_OPT from tht.cli.schema_cmd import _load_config_or_exit @@ -22,7 +23,7 @@ def migrate_cmd( try: status = migration_status(database_url) if status_only else migrate(database_url) - except (MigrationError, ValueError) as exc: + except (MigrationError, SQLAlchemyError, ValueError) as exc: if json_output: typer.echo(json.dumps({"error": str(exc)}, sort_keys=True)) else: