177 lines
6.0 KiB
Python
177 lines
6.0 KiB
Python
# ruff: noqa: INP001
|
|
"""Tests for local Codex CLI session parsing."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from app.services import codex_session_reader as reader
|
|
|
|
|
|
def _write_jsonl(path: Path, records: list[dict]) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
"\n".join(json.dumps(record) for record in records) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def _fixture_records(session_id: str = "session-1") -> list[dict]:
|
|
return [
|
|
{
|
|
"timestamp": "2026-05-20T12:00:00Z",
|
|
"type": "session_meta",
|
|
"payload": {
|
|
"id": session_id,
|
|
"cwd": "/work/pipeline",
|
|
"cli_version": "0.133.0",
|
|
"source": "vscode",
|
|
"git": {"branch": "feature/codex"},
|
|
},
|
|
},
|
|
{
|
|
"timestamp": "2026-05-20T12:00:01Z",
|
|
"type": "turn_context",
|
|
"payload": {"type": "turn_context", "cwd": "/work/pipeline", "model": "gpt-5.5"},
|
|
},
|
|
{
|
|
"timestamp": "2026-05-20T12:00:02Z",
|
|
"type": "response_item",
|
|
"payload": {
|
|
"type": "message",
|
|
"role": "user",
|
|
"content": [{"type": "input_text", "text": "Please inspect app.py"}],
|
|
},
|
|
},
|
|
{
|
|
"timestamp": "2026-05-20T12:00:03Z",
|
|
"type": "response_item",
|
|
"payload": {
|
|
"type": "message",
|
|
"role": "assistant",
|
|
"content": [{"type": "output_text", "text": "I will inspect it."}],
|
|
},
|
|
},
|
|
{
|
|
"timestamp": "2026-05-20T12:00:04Z",
|
|
"type": "response_item",
|
|
"payload": {
|
|
"type": "function_call",
|
|
"call_id": "call-1",
|
|
"name": "exec_command",
|
|
"arguments": json.dumps(
|
|
{
|
|
"cmd": "OPENAI_API_KEY=sk-testsecret1234567890 pytest",
|
|
"headers": {"Authorization": "Bearer abcdef1234567890"},
|
|
"url": "https://example.test/path?token=secret-token",
|
|
}
|
|
),
|
|
},
|
|
},
|
|
{
|
|
"timestamp": "2026-05-20T12:00:05Z",
|
|
"type": "response_item",
|
|
"payload": {
|
|
"type": "function_call_output",
|
|
"call_id": "call-1",
|
|
"output": "ok with sk-outputsecret1234567890",
|
|
},
|
|
},
|
|
{
|
|
"timestamp": "2026-05-20T12:00:06Z",
|
|
"type": "response_item",
|
|
"payload": {
|
|
"type": "function_call",
|
|
"call_id": "call-2",
|
|
"name": "apply_patch",
|
|
"arguments": "*** Begin Patch\n*** Update File: app.py\n+print('x')\n*** End Patch",
|
|
},
|
|
},
|
|
{
|
|
"timestamp": "2026-05-20T12:00:07Z",
|
|
"type": "event_msg",
|
|
"payload": {
|
|
"type": "token_count",
|
|
"info": {
|
|
"last_token_usage": {
|
|
"input_tokens": 10,
|
|
"cached_input_tokens": 4,
|
|
"output_tokens": 3,
|
|
},
|
|
"total_token_usage": {
|
|
"input_tokens": 100,
|
|
"cached_input_tokens": 40,
|
|
"output_tokens": 30,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]
|
|
|
|
|
|
def test_codex_sessions_parse_normalized_shape(tmp_path: Path, monkeypatch) -> None:
|
|
root = tmp_path / "sessions"
|
|
_write_jsonl(root / "2026/05/20/rollout-test.jsonl", _fixture_records())
|
|
monkeypatch.setenv("CODEX_SESSIONS_PATH", str(root))
|
|
|
|
sessions = reader.list_sessions()
|
|
|
|
assert len(sessions) == 1
|
|
session = sessions[0]
|
|
assert session.session_id == "session-1"
|
|
assert session.source == "codex_cli"
|
|
assert session.provider_label == "Codex CLI"
|
|
assert session.project_dir == "pipeline"
|
|
assert session.models == ["gpt-5.5"]
|
|
assert session.tokens.total == 170
|
|
assert session.git_branch == "feature/codex"
|
|
assert session.entrypoints == ["codex-cli", "vscode"]
|
|
|
|
|
|
def test_codex_messages_redact_and_paginate(tmp_path: Path, monkeypatch) -> None:
|
|
root = tmp_path / "sessions"
|
|
_write_jsonl(root / "rollout-test.jsonl", _fixture_records())
|
|
monkeypatch.setenv("CODEX_SESSIONS_PATH", str(root))
|
|
|
|
page = reader.get_session_messages("session-1", limit=1, offset=1)
|
|
|
|
assert page is not None
|
|
messages, total = page
|
|
assert total == 2
|
|
assert len(messages) == 1
|
|
assistant = messages[0]
|
|
assert assistant.role == "assistant"
|
|
assert assistant.tokens is not None
|
|
assert assistant.tokens.input == 10
|
|
assert assistant.tool_uses[0].input["cmd"] == "OPENAI_API_KEY=[REDACTED] pytest"
|
|
assert assistant.tool_uses[0].input["headers"] == "[REDACTED]"
|
|
assert assistant.tool_uses[0].input["url"] == "https://example.test/path?token=[REDACTED]"
|
|
assert assistant.tool_uses[0].result == "ok with [REDACTED]"
|
|
|
|
|
|
def test_codex_unavailable_source_is_not_error(tmp_path: Path, monkeypatch) -> None:
|
|
missing = tmp_path / "missing"
|
|
monkeypatch.setenv("CODEX_SESSIONS_PATH", str(missing))
|
|
|
|
assert reader.list_sessions() == []
|
|
metadata = reader.source_metadata()
|
|
assert metadata.source_status == "unavailable"
|
|
assert "not found" in (metadata.unavailable_reason or "")
|
|
|
|
|
|
def test_codex_tool_analytics(tmp_path: Path, monkeypatch) -> None:
|
|
root = tmp_path / "sessions"
|
|
session_path = root / "rollout-test.jsonl"
|
|
_write_jsonl(session_path, _fixture_records())
|
|
os.utime(session_path, None)
|
|
monkeypatch.setenv("CODEX_SESSIONS_PATH", str(root))
|
|
|
|
analytics = reader.get_tool_analytics(days=30)
|
|
|
|
assert analytics["tool_counts"] == {"exec_command": 1, "apply_patch": 1}
|
|
assert analytics["top_commands"] == [{"command": "pytest", "count": 1}]
|
|
assert analytics["top_files_written"] == [{"path": "app.py", "count": 1}]
|
|
assert analytics["session_count"] == 1
|