File size: 4,360 Bytes
5ef3875
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import pytest

from ycode import __version__, cli
from ycode.llm.base import LLMProvider

from tests.conftest import FakeProvider, reply


def test_version(capsys):
    with pytest.raises(SystemExit) as exc:
        cli.main(["--version"])
    assert exc.value.code == 0
    assert __version__ in capsys.readouterr().out


def test_missing_api_key_exits_cleanly(workspace, capsys, monkeypatch, tmp_path):
    monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "xdg"))
    code = cli.main(["-C", str(workspace), "--provider", "anthropic", "do something"])
    out = capsys.readouterr().out
    assert code == 1
    assert "ANTHROPIC_API_KEY" in out
    assert "Traceback" not in out
    assert "AI Coding Agent" in out  # the banner still shows


def test_default_needs_no_api_key(workspace, capsys, monkeypatch):
    """Out of the box, YCode answers with its own bundled model: no key, no training."""
    import ycode.llm.local as local
    from ycode.config import DEFAULT_BUNDLED_MODEL

    seen = {}

    class FakeLM:
        num_params, version = 100_000_000, 4

        def chat(self, turns, *, on_text=None, **_kw):
            if on_text:
                on_text("Use sorted(items).")
            return "Use sorted(items)."

    def fake_load(self, device):
        seen["dir"] = self.model_dir
        return FakeLM()

    import ycode.config as config

    monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
    monkeypatch.setattr(config, "available_memory_gb", lambda: 16.0)
    monkeypatch.setattr(local.LocalProvider, "_load", fake_load)
    code = cli.main(["-C", str(workspace), "--no-banner", "how do I sort a list?"])
    assert code == 0
    assert seen["dir"].name == DEFAULT_BUNDLED_MODEL
    assert "sorted(items)" in capsys.readouterr().out


def test_bad_config_exits_cleanly(workspace, capsys, isolated_home):
    (isolated_home / "config.toml").write_text("max_steps = -1\n")
    assert cli.main(["-C", str(workspace), "x"]) == 2
    assert "max_steps" in capsys.readouterr().out


def test_missing_directory(capsys, tmp_path):
    assert cli.main(["-C", str(tmp_path / "nope"), "x"]) == 2


def test_one_shot_task(workspace, capsys, monkeypatch):
    provider = FakeProvider([reply("", [("list_directory", {})]), reply("All good.")])
    monkeypatch.setattr(cli, "create_provider", lambda config: provider)
    code = cli.main(["-C", str(workspace), "--no-banner", "--max-steps", "7", "inspect", "the", "project"])
    out = capsys.readouterr().out
    assert code == 0
    assert "All good." in out and "YCode completed the task." in out
    assert provider.requests[0]["messages"][0].text == "inspect the project"


def test_cli_flags_reach_config(workspace, monkeypatch):
    seen = {}

    def fake_create(config):
        seen["config"] = config
        return FakeProvider([reply("ok")])

    monkeypatch.setattr(cli, "create_provider", fake_create)
    cli.main(["-C", str(workspace), "--no-banner", "--model", "claude-sonnet-5-5", "--yes", "-p", "hi"])
    assert seen["config"].model == "claude-sonnet-5-5"
    assert seen["config"].approval_mode == "auto"


def test_repl_handles_commands_tasks_and_exit(workspace, monkeypatch, capsys):
    provider = FakeProvider([reply("Hello there.")])
    monkeypatch.setattr(cli, "create_provider", lambda config: provider)
    lines = iter(["/status", "say hello", "/exit"])
    monkeypatch.setattr("builtins.input", lambda prompt="": next(lines))
    assert cli.main(["-C", str(workspace), "--no-banner"]) == 0
    out = capsys.readouterr().out
    assert "Steps used" in out and "Hello there." in out and "Goodbye." in out
    assert len(provider.requests) == 1


def test_repl_survives_internal_errors_and_eof(workspace, monkeypatch, capsys):
    class Exploding(LLMProvider):
        name = "boom"

        def complete(self, **kwargs):
            raise RuntimeError("unexpected")

    monkeypatch.setattr(cli, "create_provider", lambda config: Exploding("m"))
    lines = iter(["do it"])

    def fake_input(prompt=""):
        try:
            return next(lines)
        except StopIteration:
            raise EOFError

    monkeypatch.setattr("builtins.input", fake_input)
    assert cli.main(["-C", str(workspace), "--no-banner"]) == 0
    out = capsys.readouterr().out
    assert "Internal error: RuntimeError: unexpected" in out
    assert "Traceback" not in out