diff --git a/cli/commands/analyst.py b/cli/commands/analyst.py index e93bd08..db37ef6 100644 --- a/cli/commands/analyst.py +++ b/cli/commands/analyst.py @@ -314,8 +314,17 @@ def _generate_claude_md(workspace: Path, server_url: str, token: str) -> None: resp = httpx.get(url, headers=headers, timeout=15.0) if resp.status_code == 200: rendered = resp.json().get("content") - except Exception: - pass + elif resp.status_code != 404: + typer.echo( + f" Warning: server returned {resp.status_code} for /api/welcome; " + "using minimal fallback. Tell your admin if this persists.", + err=True, + ) + except Exception as e: + typer.echo( + f" Warning: couldn't fetch welcome prompt ({e}); using minimal fallback.", + err=True, + ) if rendered is None: # Fallback for older servers — keeps the CLI usable, just less rich. diff --git a/tests/test_cli_analyst_welcome.py b/tests/test_cli_analyst_welcome.py index 5c424b7..41edc42 100644 --- a/tests/test_cli_analyst_welcome.py +++ b/tests/test_cli_analyst_welcome.py @@ -1,5 +1,6 @@ """Integration tests for da analyst setup → /api/welcome wiring.""" +import json from pathlib import Path import httpx @@ -19,9 +20,14 @@ class _MockClient: return httpx.Response(status_code=status, json=body, request=httpx.Request("GET", url)) -def test_generate_claude_md_uses_server_render(tmp_path, monkeypatch): +def _ws(tmp_path: Path) -> Path: workspace = tmp_path / "ws" (workspace / ".claude").mkdir(parents=True) + return workspace + + +def test_generate_claude_md_uses_server_render(tmp_path, monkeypatch): + workspace = _ws(tmp_path) rendered = "# CUSTOM\n\nFrom server.\n" mock = _MockClient({ "https://example.com/api/welcome?server_url=https%3A%2F%2Fexample.com": ( @@ -30,15 +36,54 @@ def test_generate_claude_md_uses_server_render(tmp_path, monkeypatch): }) monkeypatch.setattr("cli.commands.analyst.httpx", type("_M", (), {"get": mock.get})) _generate_claude_md(workspace, server_url="https://example.com", token="t") + assert (workspace / "CLAUDE.md").read_text(encoding="utf-8") == rendered + # Workspace side-effects are created on the success path too. + assert (workspace / ".claude" / "CLAUDE.local.md").exists() + settings = json.loads((workspace / ".claude" / "settings.json").read_text(encoding="utf-8")) + assert settings["model"] == "sonnet" def test_generate_claude_md_falls_back_on_404(tmp_path, monkeypatch): - workspace = tmp_path / "ws" - (workspace / ".claude").mkdir(parents=True) + workspace = _ws(tmp_path) mock = _MockClient({}) # everything 404s monkeypatch.setattr("cli.commands.analyst.httpx", type("_M", (), {"get": mock.get})) _generate_claude_md(workspace, server_url="https://example.com", token="t") body = (workspace / "CLAUDE.md").read_text(encoding="utf-8") - assert "AI Data Analyst" in body # embedded fallback contains this string + assert "AI Data Analyst" in body assert "https://example.com" in body + + +def test_generate_claude_md_falls_back_on_null_content(tmp_path, monkeypatch): + """Server returns 200 but malformed body (`content: null`). CLI must use fallback.""" + workspace = _ws(tmp_path) + mock = _MockClient({ + "https://example.com/api/welcome?server_url=https%3A%2F%2Fexample.com": ( + {"content": None}, 200 + ), + }) + monkeypatch.setattr("cli.commands.analyst.httpx", type("_M", (), {"get": mock.get})) + _generate_claude_md(workspace, server_url="https://example.com", token="t") + body = (workspace / "CLAUDE.md").read_text(encoding="utf-8") + # Embedded fallback contains these literals + assert "AI Data Analyst" in body + assert "https://example.com" in body + + +def test_generate_claude_md_warns_on_5xx(tmp_path, monkeypatch, capsys): + """500 from server → embedded fallback, with a stderr warning so operators can diagnose.""" + workspace = _ws(tmp_path) + mock = _MockClient({ + "https://example.com/api/welcome?server_url=https%3A%2F%2Fexample.com": ( + {"detail": "boom"}, 500 + ), + }) + monkeypatch.setattr("cli.commands.analyst.httpx", type("_M", (), {"get": mock.get})) + _generate_claude_md(workspace, server_url="https://example.com", token="t") + + body = (workspace / "CLAUDE.md").read_text(encoding="utf-8") + assert "AI Data Analyst" in body # fallback used + + captured = capsys.readouterr() + assert "500" in captured.err + assert "fallback" in captured.err.lower()