pyrtan / sourcev0.14.0 · main

pyrtan / tests / test_cli.py

6,008 bytes · last changed 2026-10-25 in Read sentences with rules: rewrites, typed slots, synonyms, typos, pyrtan match

1import json
2
3import pytest
4
5from pyrtan.cli import main
6from conftest import EXAMPLES, FIXTURES
7
8
9def run(capsys, *argv):
10 code = main(list(argv))
11 out = capsys.readouterr()
12 return code, out.out, out.err
13
14
15def test_check_examples(capsys):
16 code, out, _ = run(capsys, "check", str(EXAMPLES))
17 assert code == 0 and "weather: 15 nodes" in out
18
19
20def test_check_reports_errors(tmp_path, capsys):
21 (tmp_path / "bad.toml").write_text('name = "bad"\nstart = "x"\n[nodes.x]\nkind = "done"\nnext = "y"\n')
22 code, out, _ = run(capsys, "check", str(tmp_path))
23 assert code == 1 and "error" in out
24
25
26def test_list(capsys):
27 code, out, _ = run(capsys, "list", str(EXAMPLES))
28 assert code == 0 and "top {count:int} stories on hacker news" in out
29 code, out, _ = run(capsys, "list", "--json", str(EXAMPLES))
30 assert {t["name"] for t in json.loads(out)["trees"]} >= {"weather"}
31
32
33def test_run_with_replay_text_and_json(capsys):
34 code, out, _ = run(capsys, "run", "weather in Oslo", "--trees", str(EXAMPLES), "--replay", str(FIXTURES / "examples.json"))
35 assert code == 0
36 assert "trace: weather" in out and out.strip().endswith("wind 12.3 km/h.")
37 code, out, _ = run(capsys, "run", "--tree", "hacker-news-item", "--input", "id=101", "--trees", str(EXAMPLES), "--replay", str(FIXTURES / "examples.json"), "--json", "--no-trace")
38 assert code == 0
39 data = json.loads(out)
40 assert data["output"]["title"] == "Pyrtan 0.1" and "trace" not in data
41
42
43def test_run_exit_codes(capsys):
44 fixture = str(FIXTURES / "examples.json")
45 assert run(capsys, "run", "is broken.example up", "--trees", str(EXAMPLES), "--replay", fixture)[0] == 1
46 assert run(capsys, "run", "nothing here", "--trees", str(EXAMPLES), "--replay", fixture)[0] == 3
47 assert run(capsys, "run", "--trees", str(EXAMPLES))[0] == 2
48
49
50def test_record_writes_a_fixture(tmp_path, capsys):
51 target = tmp_path / "rec.json"
52 code, _, _ = run(capsys, "run", "hacker news item 101", "--trees", str(EXAMPLES), "--replay", str(FIXTURES / "examples.json"), "--record", str(target), "--no-trace")
53 assert code == 0
54 assert "GET https://hacker-news.firebaseio.com/v0/item/101.json" in json.loads(target.read_text())
55
56
57def test_new_scaffolds_a_tree_that_checks(tmp_path, capsys):
58 code, out, _ = run(capsys, "new", "my-thing", "--dir", str(tmp_path))
59 assert code == 0 and (tmp_path / "my-thing.toml").exists()
60 code, out, _ = run(capsys, "check", str(tmp_path / "my-thing.toml"))
61 assert code == 0, out
62 assert run(capsys, "new", "my-thing", "--dir", str(tmp_path))[0] == 1
63
64
65def test_new_from_a_sentence(tmp_path, capsys):
66 code, out, _ = run(capsys, "new", "--from", "top 3 stories on hacker news", "--dir", str(tmp_path))
67 assert code == 0 and "top {count:int} stories on hacker news" in out
68 path = tmp_path / "top-stories-hacker-news.toml"
69 assert path.exists() and "[inputs.count]" in path.read_text()
70 assert run(capsys, "check", str(path))[0] == 0
71 code, out, _ = run(capsys, "new", "hn", "--from", "top 3 stories on hacker news", "--dir", str(tmp_path))
72 assert code == 0 and (tmp_path / "hn.toml").exists()
73 assert run(capsys, "new", "--dir", str(tmp_path))[0] == 2
74
75
76def test_run_no_match_proposes_a_tree(capsys):
77 code, out, _ = run(capsys, "run", "tell me a joke", "--trees", str(EXAMPLES), "--replay", str(FIXTURES / "examples.json"), "--json")
78 assert code == 3
79 data = json.loads(out)
80 assert data["reason"] == "no-match" and data["output"]["proposal"]["name"] == "joke"
81
82
83def test_gaps_needs_a_target(capsys):
84 assert run(capsys, "gaps")[0] == 2
85 assert run(capsys, "unanswered")[0] == 2
86
87
88def test_unanswered_lists_and_scaffolds(tmp_path, capsys):
89 from pyrtan import Registry
90 from pyrtan.service import Server, Service, http_json
91
92 svc = Service(Registry.from_paths([EXAMPLES]), name="x")
93 server = Server(svc.route, quiet=True).start()
94 try:
95 code, out, _ = run(capsys, "unanswered", "--at", server.url)
96 assert code == 0 and "nothing unanswered" in out
97 http_json("POST", f"{server.url}/intent", {"text": "ping example.org"})
98 http_json("POST", f"{server.url}/intent", {"text": "ping example.org"})
99 http_json("POST", f"{server.url}/intent", {"text": "tell me a joke"})
100 code, out, _ = run(capsys, "unanswered", "--at", server.url)
101 assert code == 0 and out.startswith("'ping example.org' x2") and "would be: host-ping answering ping {host:host}" in out and "pyrtan new joke --from" in out
102 code, out, _ = run(capsys, "unanswered", "--at", server.url, "--json")
103 assert [e["text"] for e in json.loads(out)["unanswered"]] == ["ping example.org", "tell me a joke"]
104 code, out, _ = run(capsys, "unanswered", "--at", server.url, "--scaffold", str(tmp_path))
105 assert code == 0 and "wrote" in out and (tmp_path / "host-ping.toml").exists() and (tmp_path / "joke.toml").exists()
106 assert run(capsys, "check", str(tmp_path))[0] == 0
107 code, out, _ = run(capsys, "unanswered", "--at", server.url, "--scaffold", str(tmp_path))
108 assert "kept" in out and "wrote" not in out
109 finally:
110 server.stop()
111
112
113def test_match_explains_a_sentence(capsys):
114 code, out, _ = run(capsys, "match", "Hey Pyrtan, what's the forecast in Oslo please?", "--trees", str(EXAMPLES))
115 assert code == 0
116 assert "read as: 'what is the forecast in Oslo'" in out
117 assert "fillers: 'Hey Pyrtan', 'please'" in out and "contractions: what's->what is" in out
118 assert "winner: weather" in out and '"place": "Oslo"' in out
119 code, out, _ = run(capsys, "match", "is example dot com up", "--trees", str(EXAMPLES))
120 assert code == 1 and "no trigger matched" in out
121 code, out, _ = run(capsys, "match", "weather in Oslo", "--trees", str(EXAMPLES), "--json")
122 assert code == 0 and json.loads(out)["matches"][0]["tree"] == "weather"
123 assert run(capsys, "match", "x", "--trees", str(EXAMPLES), "--now", "yesterday")[0] == 2