fix(cli): fix session show/list JSON output to not redact input messages #6699
@@ -39,6 +39,11 @@ Feature: Session CLI commands
|
||||
Then the session CLI output should be valid JSON
|
||||
And the session CLI JSON should contain "sessions"
|
||||
|
||||
Scenario: List sessions JSON matches the documented contract
|
||||
Given there are mocked existing sessions
|
||||
When I run session CLI list with --format json
|
||||
Then the session CLI JSON list entries should match the documented contract
|
||||
|
||||
# Show command tests
|
||||
Scenario: Show session with valid ID
|
||||
Given there is a mocked session with messages
|
||||
@@ -59,6 +64,11 @@ Feature: Session CLI commands
|
||||
When I run session CLI show with --format json
|
||||
Then the session CLI output should be valid JSON
|
||||
|
||||
Scenario: Show session JSON includes token usage counts
|
||||
Given there is a mocked session with messages
|
||||
When I run session CLI show with --format json
|
||||
Then the session CLI JSON token usage should include counts
|
||||
|
||||
Scenario: Show session with invalid ID
|
||||
When I run session CLI show with an invalid session ID
|
||||
Then the session CLI should exit with error
|
||||
|
||||
@@ -514,6 +514,52 @@ def step_json_contains_key(context: Context, key: str) -> None:
|
||||
assert key in data, f"Key '{key}' not found in JSON: {data}"
|
||||
|
||||
|
||||
@then("the session CLI JSON token usage should include counts")
|
||||
def step_json_show_token_usage_counts(context: Context) -> None:
|
||||
parsed = json.loads(context.result.output)
|
||||
data = _unwrap_envelope(parsed)
|
||||
token_usage = data.get("token_usage")
|
||||
assert isinstance(token_usage, dict), (
|
||||
f"token_usage missing or not an object: {token_usage}"
|
||||
)
|
||||
_assert_token_usage_counts_are_ints(token_usage)
|
||||
|
||||
|
||||
@then("the session CLI JSON list entries should match the documented contract")
|
||||
def step_json_list_contract(context: Context) -> None:
|
||||
parsed = json.loads(context.result.output)
|
||||
data = _unwrap_envelope(parsed)
|
||||
sessions = data.get("sessions")
|
||||
assert isinstance(sessions, list), f"sessions missing or not a list: {sessions}"
|
||||
for index, session in enumerate(sessions):
|
||||
assert isinstance(session, dict), (
|
||||
f"session entry at index {index} is not an object: {session!r}"
|
||||
)
|
||||
expected_keys = {"id", "name", "actor", "messages", "updated"}
|
||||
actual_keys = set(session.keys())
|
||||
missing = expected_keys - actual_keys
|
||||
extra = actual_keys - expected_keys
|
||||
assert not missing, (
|
||||
f"session entry {index} missing keys {sorted(missing)}: {session!r}"
|
||||
)
|
||||
assert not extra, (
|
||||
f"session entry {index} has undocumented keys {sorted(extra)}: {session!r}"
|
||||
)
|
||||
|
||||
|
||||
def _assert_token_usage_counts_are_ints(token_usage: dict[str, Any]) -> None:
|
||||
input_tokens = token_usage.get("input_tokens")
|
||||
output_tokens = token_usage.get("output_tokens")
|
||||
assert isinstance(input_tokens, int), (
|
||||
f"input_tokens should be int, got {input_tokens!r}"
|
||||
)
|
||||
assert isinstance(output_tokens, int), (
|
||||
f"output_tokens should be int, got {output_tokens!r}"
|
||||
)
|
||||
assert input_tokens != "***REDACTED***", "input_tokens should not be redacted"
|
||||
assert output_tokens != "***REDACTED***", "output_tokens should not be redacted"
|
||||
|
||||
|
||||
@then("the session CLI should exit with error")
|
||||
def step_exit_with_error(context: Context) -> None:
|
||||
assert context.result.exit_code != 0, (
|
||||
|
||||
@@ -44,6 +44,8 @@ _FALSE_POSITIVE_KEYS: set[str] = {
|
||||
"token_count",
|
||||
"token_limit",
|
||||
"token_usage",
|
||||
"input_tokens",
|
||||
"output_tokens",
|
||||
"max_tokens",
|
||||
"total_tokens",
|
||||
"prompt_tokens",
|
||||
|
||||
Reference in New Issue
Block a user