11"""Tests for AgyIntegration (Antigravity)."""
22
3+ from specify_cli .integrations import get_integration
4+
35from .test_integration_base_skills import SkillsIntegrationTests
46
57
@@ -12,10 +14,21 @@ class TestAgyIntegration(SkillsIntegrationTests):
1214
1315 def test_options_include_skills_flag (self ):
1416 """Override inherited test: AgyIntegration should not expose a --skills flag because .agents/ is its only layout."""
15- from specify_cli .integrations import get_integration
1617 i = get_integration (self .KEY )
1718 skills_opts = [o for o in i .options () if o .name == "--skills" ]
1819 assert len (skills_opts ) == 0
20+
21+ def test_requires_cli_is_true (self ):
22+ """agy is a CLI tool; requires_cli must be True."""
23+ i = get_integration (self .KEY )
24+ assert i .config ["requires_cli" ] is True
25+
26+ def test_install_url_is_set (self ):
27+ """install_url must point to the official installation page."""
28+ i = get_integration (self .KEY )
29+ assert i .config ["install_url" ] == "https://antigravity.google/"
30+
31+
1932class TestAgyAutoPromote :
2033 """--ai agy auto-promotes to integration path."""
2134
@@ -36,10 +49,87 @@ def test_agy_setup_warning(self, tmp_path):
3649 from typer .testing import CliRunner
3750 from specify_cli import app
3851
39- # Click >= 8.2 separates stdout and stderr natively, mix_stderr is removed
52+ # Click >= 8.2 separates stdout and stderr natively
4053 runner = CliRunner ()
4154 target = tmp_path / "test-proj2"
4255 result = runner .invoke (app , ["init" , str (target ), "--ai" , "agy" , "--no-git" , "--script" , "sh" ])
4356
4457 assert result .exit_code == 0
4558 assert "Warning: The .agents/ layout requires Antigravity v1.20.5 or newer" in result .stderr
59+
60+
61+ class TestAgyBuildExecArgs :
62+ """agy non-interactive execution argument building."""
63+
64+ def test_build_exec_args_returns_print_command (self ):
65+ """build_exec_args should return ['agy', '--print', prompt]."""
66+ from specify_cli .integrations import get_integration
67+ i = get_integration ("agy" )
68+ result = i .build_exec_args ("describe my feature" )
69+ assert result == ["agy" , "--print" , "describe my feature" ]
70+
71+ def test_build_exec_args_ignores_model (self ):
72+ """agy does not support --model; model param must be ignored."""
73+ from specify_cli .integrations import get_integration
74+ i = get_integration ("agy" )
75+ result = i .build_exec_args ("my prompt" , model = "gemini-pro" )
76+ assert result == ["agy" , "--print" , "my prompt" ]
77+
78+ def test_build_exec_args_ignores_output_json (self ):
79+ """agy does not support JSON output; output_json param must be ignored."""
80+ from specify_cli .integrations import get_integration
81+ i = get_integration ("agy" )
82+ result = i .build_exec_args ("my prompt" , output_json = False )
83+ assert result == ["agy" , "--print" , "my prompt" ]
84+
85+
86+ class TestAgyHookCommandNote :
87+ """Verify dot-to-hyphen normalization note is injected into hook sections."""
88+
89+ def test_hook_note_injected_in_skills_with_hooks (self , tmp_path ):
90+ """Skills with hook sections should contain the normalization note."""
91+ from specify_cli .integrations import get_integration
92+ from specify_cli .integrations .manifest import IntegrationManifest
93+
94+ i = get_integration ("agy" )
95+ m = IntegrationManifest ("agy" , tmp_path )
96+ i .setup (tmp_path , m , script_type = "sh" )
97+ specify_skill = tmp_path / ".agents/skills/speckit-specify/SKILL.md"
98+ assert specify_skill .exists ()
99+ content = specify_skill .read_text (encoding = "utf-8" )
100+ assert "replace dots" in content , (
101+ "speckit-specify should have dot-to-hyphen hook note"
102+ )
103+
104+ def test_hook_note_not_in_skills_without_hooks (self ):
105+ """Skills without hook sections should not get the note."""
106+ from specify_cli .integrations .agy import AgyIntegration
107+
108+ content = "---\n name: test\n description: test\n ---\n \n No hooks here.\n "
109+ result = AgyIntegration ._inject_hook_command_note (content )
110+ assert "replace dots" not in result
111+
112+ def test_hook_note_idempotent (self ):
113+ """Injecting the note twice must not duplicate it."""
114+ from specify_cli .integrations .agy import AgyIntegration
115+
116+ content = (
117+ "---\n name: test\n ---\n \n "
118+ "- For each executable hook, output the following based on its flag:\n "
119+ )
120+ once = AgyIntegration ._inject_hook_command_note (content )
121+ twice = AgyIntegration ._inject_hook_command_note (once )
122+ assert once == twice , "Hook note injection should be idempotent"
123+
124+ def test_hook_note_preserves_indentation (self ):
125+ """The injected note must match the indentation of the target line."""
126+ from specify_cli .integrations .agy import AgyIntegration
127+
128+ content = (
129+ "---\n name: test\n ---\n \n "
130+ " - For each executable hook, output the following\n "
131+ )
132+ result = AgyIntegration ._inject_hook_command_note (content )
133+ lines = result .splitlines ()
134+ note_line = [l for l in lines if "replace dots" in l ][0 ]
135+ assert note_line .startswith (" " ), "Note should preserve indentation"
0 commit comments