6,008 bytes · last changed 2026-10-25 in Read sentences with rules: rewrites, typed slots, synonyms, typos, pyrtan match
| 1 | import json |
| 2 | |
| 3 | import pytest |
| 4 | |
| 5 | from pyrtan.cli import main |
| 6 | from conftest import EXAMPLES, FIXTURES |
| 7 | |
| 8 | |
| 9 | def run(capsys, *argv): |
| 10 | code = main(list(argv)) |
| 11 | out = capsys.readouterr() |
| 12 | return code, out.out, out.err |
| 13 | |
| 14 | |
| 15 | def test_check_examples(capsys): |
| 16 | code, out, _ = run(capsys, "check", str(EXAMPLES)) |
| 17 | assert code == 0 and "weather: 15 nodes" in out |
| 18 | |
| 19 | |
| 20 | def 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 | |
| 26 | def 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 | |
| 33 | def 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 | |
| 43 | def 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 | |
| 50 | def 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 | |
| 57 | def 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 | |
| 65 | def 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 | |
| 76 | def 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 | |
| 83 | def test_gaps_needs_a_target(capsys): |
| 84 | assert run(capsys, "gaps")[0] == 2 |
| 85 | assert run(capsys, "unanswered")[0] == 2 |
| 86 | |
| 87 | |
| 88 | def 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 | |
| 113 | def 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 |