add alias to avoid explicit python commands, scheduled runs and bug fixes
This commit is contained in:
@@ -415,3 +415,123 @@ class TestProjectsCommand:
|
||||
assert result.exit_code == 0
|
||||
env_text = (Path(fs) / ".env").read_text(encoding="utf-8")
|
||||
assert "CHATGPT_PROJECT_IDS=g-p-new" in env_text
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# sync command + non-interactive ToS gate
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestSyncCommand:
|
||||
"""`sync` chains export → joplin for schedulers, with a real exit code."""
|
||||
|
||||
def _cache(self, tmp_path) -> Cache:
|
||||
cache = Cache(tmp_path)
|
||||
cache.acknowledge_tos()
|
||||
# Non-empty last_run so the first-run doctor gate stays out of the way.
|
||||
cache.mark_exported("codex", "dummy", {"updated_at": "2024-01-01T00:00:00Z"})
|
||||
return cache
|
||||
|
||||
def _env(self, tmp_path) -> dict:
|
||||
"""A real (minimal) codex session — `export` exits 1 on no providers at
|
||||
all, so an empty directory would test the wrong failure."""
|
||||
import json
|
||||
|
||||
day = tmp_path / "sessions" / "2026" / "08" / "17"
|
||||
day.mkdir(parents=True, exist_ok=True)
|
||||
sid = "01a00e3f-a309-74a3-bf32-06c2cd87faa3"
|
||||
records = [
|
||||
{
|
||||
"timestamp": "2026-08-17T05:44:06.666Z",
|
||||
"type": "session_meta",
|
||||
"payload": {"session_id": sid, "timestamp": "2026-08-17T05:44:06.666Z",
|
||||
"cwd": str(tmp_path / "ws")},
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-08-17T05:44:09.000Z",
|
||||
"type": "event_msg",
|
||||
"payload": {"type": "item_completed", "item": {
|
||||
"type": "UserMessage", "id": "u1",
|
||||
"content": [{"type": "text", "text": "hello"}]}},
|
||||
},
|
||||
]
|
||||
(day / f"rollout-2026-08-17T01-44-06-{sid}.jsonl").write_text(
|
||||
"\n".join(json.dumps(r) for r in records), encoding="utf-8"
|
||||
)
|
||||
return {
|
||||
"CACHE_DIR": str(tmp_path),
|
||||
"EXPORT_DIR": str(tmp_path / "exports"),
|
||||
"CODEX_DIR": str(tmp_path / "sessions"),
|
||||
}
|
||||
|
||||
def test_skip_joplin_exits_zero(self, tmp_path):
|
||||
self._cache(tmp_path)
|
||||
result = CliRunner(mix_stderr=True).invoke(
|
||||
cli,
|
||||
["--no-log-file", "sync", "--provider", "codex", "--skip-joplin"],
|
||||
env=self._env(tmp_path),
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Skipping Joplin sync" in result.output
|
||||
assert "Sync complete" in result.output
|
||||
|
||||
def test_joplin_optional_survives_unreachable_joplin(self, tmp_path):
|
||||
"""Joplin being closed must not fail a scheduled run — the export is done."""
|
||||
self._cache(tmp_path)
|
||||
result = CliRunner(mix_stderr=True).invoke(
|
||||
cli,
|
||||
["--no-log-file", "sync", "--provider", "codex", "--joplin-optional"],
|
||||
env={**self._env(tmp_path), "JOPLIN_API_URL": "http://127.0.0.1:9"},
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Joplin sync skipped" in result.output
|
||||
|
||||
def test_unreachable_joplin_fails_without_the_flag(self, tmp_path):
|
||||
self._cache(tmp_path)
|
||||
result = CliRunner(mix_stderr=True).invoke(
|
||||
cli,
|
||||
["--no-log-file", "sync", "--provider", "codex"],
|
||||
env={**self._env(tmp_path), "JOPLIN_API_URL": "http://127.0.0.1:9"},
|
||||
)
|
||||
assert result.exit_code == 1
|
||||
|
||||
def test_export_failures_set_nonzero_exit(self, tmp_path, monkeypatch):
|
||||
"""A scheduler must be able to tell a real run from a silent no-op."""
|
||||
self._cache(tmp_path)
|
||||
import src.main as main_mod
|
||||
|
||||
real_export = main_mod.export.callback
|
||||
|
||||
def fake_export(*args, **kwargs):
|
||||
import click
|
||||
ctx = click.get_current_context()
|
||||
ctx.obj["last_export_summary"] = {
|
||||
"codex": {"exported": 0, "skipped": 0, "failed": 3}
|
||||
}
|
||||
|
||||
monkeypatch.setattr(main_mod.export, "callback", fake_export)
|
||||
try:
|
||||
result = CliRunner(mix_stderr=True).invoke(
|
||||
cli,
|
||||
["--no-log-file", "sync", "--provider", "codex", "--skip-joplin"],
|
||||
env=self._env(tmp_path),
|
||||
)
|
||||
finally:
|
||||
monkeypatch.setattr(main_mod.export, "callback", real_export)
|
||||
assert result.exit_code == 1
|
||||
assert "3 conversation(s) failed to export" in result.output
|
||||
|
||||
|
||||
class TestNonInteractiveTosGate:
|
||||
"""Without a TTY the gate must fail loudly, not exit 0 having done nothing."""
|
||||
|
||||
def test_no_tty_exits_one_with_explanation(self, tmp_path, monkeypatch):
|
||||
Cache(tmp_path) # fresh cache: ToS not acknowledged
|
||||
monkeypatch.setattr("sys.stdin.isatty", lambda: False)
|
||||
result = CliRunner(mix_stderr=True).invoke(
|
||||
cli,
|
||||
["--no-log-file", "doctor"],
|
||||
env={"CACHE_DIR": str(tmp_path), "EXPORT_DIR": str(tmp_path / "exports")},
|
||||
)
|
||||
assert result.exit_code == 1
|
||||
assert "no terminal to" in result.output
|
||||
|
||||
Reference in New Issue
Block a user