-
Notifications
You must be signed in to change notification settings - Fork 12.1k
Expand file tree
/
Copy pathtest_integration_agy.py
More file actions
206 lines (165 loc) 路 9.14 KB
/
Copy pathtest_integration_agy.py
File metadata and controls
206 lines (165 loc) 路 9.14 KB
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""Tests for AgyIntegration (Antigravity)."""
from specify_cli.integrations import get_integration
from .test_integration_base_skills import SkillsIntegrationTests
class TestAgyIntegration(SkillsIntegrationTests):
KEY = "agy"
FOLDER = ".agents/"
COMMANDS_SUBDIR = "skills"
REGISTRAR_DIR = ".agents/skills"
def test_options_include_skills_flag(self):
"""Override inherited test: AgyIntegration should not expose a --skills flag because .agents/ is its only layout."""
i = get_integration(self.KEY)
skills_opts = [o for o in i.options() if o.name == "--skills"]
assert len(skills_opts) == 0
def test_requires_cli_is_true(self):
"""agy is a CLI tool; requires_cli must be True."""
i = get_integration(self.KEY)
assert i.config["requires_cli"] is True
def test_install_url_is_set(self):
"""install_url must point to the official installation page."""
i = get_integration(self.KEY)
assert i.config["install_url"] == "https://antigravity.google/"
class TestAgyInitFlow:
"""--integration agy creates expected files."""
def test_integration_agy_creates_skills(self, tmp_path):
"""--integration agy should create skills directory."""
from typer.testing import CliRunner
from specify_cli import app
runner = CliRunner()
target = tmp_path / "test-proj"
result = runner.invoke(app, ["init", str(target), "--integration", "agy", "--script", "sh", "--ignore-agent-tools"])
assert result.exit_code == 0, f"init --integration agy failed: {result.output}"
assert (target / ".agents" / "skills" / "speckit-plan" / "SKILL.md").exists()
def test_agy_setup_warning(self, tmp_path):
"""Agy integration should print a warning about v1.20.5 requirement during setup."""
from typer.testing import CliRunner
from specify_cli import app
# Click >= 8.2 separates stdout and stderr natively
runner = CliRunner()
target = tmp_path / "test-proj2"
result = runner.invoke(app, ["init", str(target), "--integration", "agy", "--script", "sh", "--ignore-agent-tools"])
assert result.exit_code == 0
assert "Warning: The .agents/ layout requires Antigravity v1.20.5 or newer" in result.stderr
class TestAgyBuildExecArgs:
"""agy non-interactive execution argument building."""
def test_build_exec_args_returns_print_command(self):
"""build_exec_args should return ['agy', '--print', prompt]."""
from specify_cli.integrations import get_integration
i = get_integration("agy")
result = i.build_exec_args("describe my feature")
assert result == ["agy", "--print", "describe my feature"]
def test_build_exec_args_honors_model(self):
"""agy >=1.20 supports --model; it must be prepended before --print."""
from specify_cli.integrations import get_integration
i = get_integration("agy")
result = i.build_exec_args("my prompt", model="gemini-pro")
assert result == ["agy", "--model", "gemini-pro", "--print", "my prompt"]
def test_build_exec_args_no_model_flag_when_model_is_none(self):
"""When model is None, no --model flag should appear in the args."""
from specify_cli.integrations import get_integration
i = get_integration("agy")
result = i.build_exec_args("my prompt", model=None)
assert "--model" not in result
def test_build_exec_args_ignores_output_json(self):
"""agy does not support JSON output; output_json param must be ignored."""
from specify_cli.integrations import get_integration
i = get_integration("agy")
result = i.build_exec_args("my prompt", output_json=False)
assert result == ["agy", "--print", "my prompt"]
def test_build_exec_args_extra_args_before_print(self, monkeypatch):
"""SPECKIT_INTEGRATION_AGY_EXTRA_ARGS must be inserted BEFORE --print.
agy treats every token after --print as part of the prompt string,
not as CLI flags. Appending flags after --print (the previous
behaviour) caused them to be silently absorbed into the prompt.
See issue #4480.
"""
from specify_cli.integrations import get_integration
monkeypatch.setenv("SPECKIT_INTEGRATION_AGY_EXTRA_ARGS", "--verbose")
i = get_integration("agy")
result = i.build_exec_args("my prompt")
# --verbose must appear before --print
assert result.index("--verbose") < result.index("--print")
assert result == ["agy", "--verbose", "--print", "my prompt"]
def test_build_exec_args_add_dir_for_workspace(self, tmp_path):
"""--add-dir <project_root> must be injected before --print when project_root is given.
Without --add-dir, agy cannot locate .agents/skills/ and reports
'no active workspace', ignoring installed Spec Kit skills entirely.
See issue #4480.
"""
from specify_cli.integrations import get_integration
i = get_integration("agy")
result = i.build_exec_args("my prompt", project_root=tmp_path)
assert "--add-dir" in result
add_dir_idx = result.index("--add-dir")
print_idx = result.index("--print")
assert add_dir_idx < print_idx, "--add-dir must come before --print"
assert result[add_dir_idx + 1] == str(tmp_path)
def test_build_exec_args_no_add_dir_when_project_root_is_none(self):
"""When project_root is None, --add-dir must not appear."""
from specify_cli.integrations import get_integration
i = get_integration("agy")
result = i.build_exec_args("my prompt", project_root=None)
assert "--add-dir" not in result
def test_build_exec_args_combined_flag_order(self, monkeypatch, tmp_path):
"""When model, project_root, and EXTRA_ARGS are all set, order must be:
agy --model <m> --add-dir <d> <extra-args> --print <prompt>.
"""
from specify_cli.integrations import get_integration
monkeypatch.setenv("SPECKIT_INTEGRATION_AGY_EXTRA_ARGS", "--dangerously-skip-permissions")
i = get_integration("agy")
result = i.build_exec_args("hello", model="claude-3", project_root=tmp_path)
assert result[0] == "agy"
assert "--model" in result
assert "--add-dir" in result
assert "--dangerously-skip-permissions" in result
print_idx = result.index("--print")
for flag in ("--model", "--add-dir", "--dangerously-skip-permissions"):
assert result.index(flag) < print_idx, f"{flag} must appear before --print"
assert result[-1] == "hello"
def test_build_exec_args_honors_executable_override(self, monkeypatch):
from specify_cli.integrations import get_integration
monkeypatch.setenv("SPECKIT_INTEGRATION_AGY_EXECUTABLE", "/custom/agy")
i = get_integration("agy")
assert i.build_exec_args("my prompt")[0] == "/custom/agy"
class TestAgyHookCommandNote:
"""Verify dot-to-hyphen normalization note is injected into hook sections."""
def test_hook_note_injected_in_skills_with_hooks(self, tmp_path):
"""Skills with hook sections should contain the normalization note."""
from specify_cli.integrations import get_integration
from specify_cli.integrations.manifest import IntegrationManifest
i = get_integration("agy")
m = IntegrationManifest("agy", tmp_path)
i.setup(tmp_path, m, script_type="sh")
specify_skill = tmp_path / ".agents/skills/speckit-specify/SKILL.md"
assert specify_skill.exists()
content = specify_skill.read_text(encoding="utf-8")
assert "replace dots" in content, (
"speckit-specify should have dot-to-hyphen hook note"
)
def test_hook_note_not_in_skills_without_hooks(self):
"""Skills without hook sections should not get the note."""
from specify_cli.integrations.agy import AgyIntegration
content = "---\nname: test\ndescription: test\n---\n\nNo hooks here.\n"
result = AgyIntegration._inject_hook_command_note(content)
assert "replace dots" not in result
def test_hook_note_idempotent(self):
"""Injecting the note twice must not duplicate it."""
from specify_cli.integrations.agy import AgyIntegration
content = (
"---\nname: test\n---\n\n"
"- For each executable hook, output the following based on its flag:\n"
)
once = AgyIntegration._inject_hook_command_note(content)
twice = AgyIntegration._inject_hook_command_note(once)
assert once == twice, "Hook note injection should be idempotent"
def test_hook_note_preserves_indentation(self):
"""The injected note must match the indentation of the target line."""
from specify_cli.integrations.agy import AgyIntegration
content = (
"---\nname: test\n---\n\n"
" - For each executable hook, output the following\n"
)
result = AgyIntegration._inject_hook_command_note(content)
lines = result.splitlines()
note_line = [ln for ln in lines if "replace dots" in ln][0]
assert note_line.startswith(" "), "Note should preserve indentation"