Skip to content

Commit 035a569

Browse files
authored
chore: bump version to 2.2.0 (#6)
* chore: bump version to 2.2.0 Align with capiscio-core for unified versioning. All capiscio packages now share the same version number. * fix(python): update version and remove unused imports
1 parent 506637d commit 035a569

7 files changed

Lines changed: 500 additions & 50 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.2.0] - 2025-12-10
9+
10+
### Changed
11+
- **VERSION ALIGNMENT**: All CapiscIO packages now share the same version number.
12+
- `capiscio-core`, `capiscio` (npm), and `capiscio` (PyPI) are all v2.2.0.
13+
- Simplifies compatibility - no version matrix needed.
14+
- **CORE VERSION**: Now downloads `capiscio-core` v2.2.0.
15+
16+
### Added
17+
- **Test Suite**: Added comprehensive test coverage (96%) for CLI wrapper and binary manager.
18+
819
## [2.1.3] - 2025-11-21
920

1021
### Fixed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "capiscio"
7-
version = "2.1.3"
7+
version = "2.2.0"
88
description = "The official CapiscIO CLI tool for validating A2A agents."
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/capiscio/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""CapiscIO CLI package."""
22

3-
__version__ = "0.1.0"
3+
__version__ = "2.2.0"

src/capiscio/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
logger = logging.getLogger(__name__)
1818

1919
# Configuration
20-
CORE_VERSION = "1.0.2" # The version of the core binary to download
20+
CORE_VERSION = "2.2.0" # The version of the core binary to download
2121
GITHUB_REPO = "capiscio/capiscio-core"
2222
BINARY_NAME = "capiscio"
2323

tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Pytest configuration for capiscio-python tests."""
2+
import sys
3+
from pathlib import Path
4+
5+
# Add src directory to path so tests can import capiscio
6+
src_path = Path(__file__).parent.parent / "src"
7+
sys.path.insert(0, str(src_path))

tests/test_cli.py

Lines changed: 225 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,237 @@
1+
"""Tests for capiscio.cli module."""
12
import sys
23
from unittest.mock import patch, MagicMock
34
import pytest
45
from capiscio.cli import main
56

6-
def test_cli_pass_through():
7-
"""
8-
Verify that arguments passed to the CLI are forwarded
9-
exactly as-is to the run_core function.
10-
"""
11-
test_args = ["capiscio", "validate", "https://example.com", "--verbose"]
12-
13-
# Mock sys.argv
14-
with patch.object(sys, 'argv', test_args):
15-
# Mock run_core to avoid actual execution/download
16-
with patch('capiscio.cli.run_core') as mock_run_core:
17-
# Mock sys.exit to prevent test from exiting
18-
with patch.object(sys, 'exit') as mock_exit:
19-
main()
20-
21-
# Check that run_core was called with the correct arguments
22-
# sys.argv[1:] slices off the script name ("capiscio")
23-
expected_args = ["validate", "https://example.com", "--verbose"]
24-
mock_run_core.assert_called_once_with(expected_args)
25-
26-
def test_wrapper_version_flag():
27-
"""Verify that --wrapper-version is intercepted and not passed to core."""
28-
test_args = ["capiscio", "--wrapper-version"]
29-
30-
with patch.object(sys, 'argv', test_args):
31-
with patch('capiscio.cli.run_core') as mock_run_core:
32-
with patch.object(sys, 'exit') as mock_exit:
33-
# We need to mock importlib.metadata.version since package might not be installed
34-
with patch('importlib.metadata.version', return_value="1.2.3"):
7+
8+
class TestMainCLI:
9+
"""Tests for the main CLI entry point."""
10+
11+
def test_cli_pass_through(self):
12+
"""
13+
Verify that arguments passed to the CLI are forwarded
14+
exactly as-is to the run_core function.
15+
"""
16+
test_args = ["capiscio", "validate", "https://example.com", "--verbose"]
17+
18+
# Mock sys.argv
19+
with patch.object(sys, 'argv', test_args):
20+
# Mock run_core to avoid actual execution/download
21+
with patch('capiscio.cli.run_core') as mock_run_core:
22+
# Mock sys.exit to prevent test from exiting
23+
with patch.object(sys, 'exit') as mock_exit:
3524
main()
3625

37-
# Should NOT call run_core
38-
mock_run_core.assert_not_called()
39-
# Should exit with 0
40-
mock_exit.assert_called_with(0)
26+
# Check that run_core was called with the correct arguments
27+
# sys.argv[1:] slices off the script name ("capiscio")
28+
expected_args = ["validate", "https://example.com", "--verbose"]
29+
mock_run_core.assert_called_once_with(expected_args)
4130

42-
def test_wrapper_clean_flag():
43-
"""Verify that --wrapper-clean is intercepted."""
44-
test_args = ["capiscio", "--wrapper-clean"]
45-
46-
with patch.object(sys, 'argv', test_args):
47-
with patch('capiscio.cli.run_core') as mock_run_core:
48-
with patch.object(sys, 'exit') as mock_exit:
49-
with patch('shutil.rmtree') as mock_rmtree:
50-
with patch('capiscio.cli.get_cache_dir') as mock_get_dir:
51-
mock_dir = MagicMock()
52-
mock_dir.exists.return_value = True
53-
mock_get_dir.return_value = mock_dir
54-
31+
def test_cli_empty_args(self):
32+
"""Test CLI with no arguments passes empty list to run_core."""
33+
test_args = ["capiscio"]
34+
35+
with patch.object(sys, 'argv', test_args):
36+
with patch('capiscio.cli.run_core') as mock_run_core:
37+
with patch.object(sys, 'exit'):
38+
main()
39+
mock_run_core.assert_called_once_with([])
40+
41+
42+
class TestWrapperCommands:
43+
"""Tests for wrapper-specific commands."""
44+
45+
def test_wrapper_version_flag(self):
46+
"""Verify that --wrapper-version is intercepted and not passed to core."""
47+
test_args = ["capiscio", "--wrapper-version"]
48+
49+
with patch.object(sys, 'argv', test_args):
50+
with patch('capiscio.cli.run_core') as mock_run_core:
51+
with patch.object(sys, 'exit') as mock_exit:
52+
# We need to mock importlib.metadata.version since package might not be installed
53+
with patch('importlib.metadata.version', return_value="1.2.3"):
5554
main()
5655

57-
mock_rmtree.assert_called_once()
56+
# Should NOT call run_core
5857
mock_run_core.assert_not_called()
58+
# Should exit with 0
5959
mock_exit.assert_called_with(0)
60+
61+
def test_wrapper_version_unknown(self):
62+
"""Test --wrapper-version when version cannot be determined."""
63+
test_args = ["capiscio", "--wrapper-version"]
64+
65+
with patch.object(sys, 'argv', test_args):
66+
with patch('capiscio.cli.run_core') as mock_run_core:
67+
with patch.object(sys, 'exit') as mock_exit:
68+
with patch('importlib.metadata.version', side_effect=Exception("Not found")):
69+
with patch('capiscio.cli.console') as mock_console:
70+
main()
71+
72+
mock_run_core.assert_not_called()
73+
# Should still print something about version
74+
mock_console.print.assert_called()
75+
mock_exit.assert_called_with(0)
76+
77+
def test_wrapper_clean_flag(self):
78+
"""Verify that --wrapper-clean is intercepted."""
79+
test_args = ["capiscio", "--wrapper-clean"]
80+
81+
with patch.object(sys, 'argv', test_args):
82+
with patch('capiscio.cli.run_core') as mock_run_core:
83+
with patch.object(sys, 'exit') as mock_exit:
84+
with patch('shutil.rmtree') as mock_rmtree:
85+
with patch('capiscio.cli.get_cache_dir') as mock_get_dir:
86+
mock_dir = MagicMock()
87+
mock_dir.exists.return_value = True
88+
mock_get_dir.return_value = mock_dir
89+
90+
main()
91+
92+
mock_rmtree.assert_called_once()
93+
mock_run_core.assert_not_called()
94+
mock_exit.assert_called_with(0)
95+
96+
def test_wrapper_clean_nonexistent_dir(self):
97+
"""Test --wrapper-clean when cache directory doesn't exist."""
98+
test_args = ["capiscio", "--wrapper-clean"]
99+
100+
with patch.object(sys, 'argv', test_args):
101+
with patch('capiscio.cli.run_core') as mock_run_core:
102+
with patch.object(sys, 'exit') as mock_exit:
103+
with patch('shutil.rmtree') as mock_rmtree:
104+
with patch('capiscio.cli.get_cache_dir') as mock_get_dir:
105+
with patch('capiscio.cli.console') as mock_console:
106+
mock_dir = MagicMock()
107+
mock_dir.exists.return_value = False
108+
mock_get_dir.return_value = mock_dir
109+
110+
main()
111+
112+
mock_rmtree.assert_not_called()
113+
mock_run_core.assert_not_called()
114+
mock_exit.assert_called_with(0)
115+
116+
def test_wrapper_clean_error(self):
117+
"""Test --wrapper-clean when cleanup fails."""
118+
test_args = ["capiscio", "--wrapper-clean"]
119+
120+
with patch.object(sys, 'argv', test_args):
121+
with patch('capiscio.cli.run_core') as mock_run_core:
122+
with patch.object(sys, 'exit') as mock_exit:
123+
with patch('shutil.rmtree', side_effect=PermissionError("Access denied")):
124+
with patch('capiscio.cli.get_cache_dir') as mock_get_dir:
125+
with patch('capiscio.cli.console') as mock_console:
126+
mock_dir = MagicMock()
127+
mock_dir.exists.return_value = True
128+
mock_get_dir.return_value = mock_dir
129+
130+
main()
131+
132+
mock_run_core.assert_not_called()
133+
# Should exit with 1 on error
134+
mock_exit.assert_called_with(1)
135+
136+
def test_unknown_wrapper_command_returns(self):
137+
"""Test that unknown --wrapper-* commands don't crash."""
138+
test_args = ["capiscio", "--wrapper-unknown"]
139+
140+
with patch.object(sys, 'argv', test_args):
141+
with patch('capiscio.cli.run_core') as mock_run_core:
142+
with patch.object(sys, 'exit'):
143+
# Should return early, not call run_core
144+
main()
145+
mock_run_core.assert_not_called()
146+
147+
148+
class TestCommandDelegation:
149+
"""Tests for command delegation to core binary."""
150+
151+
def test_validate_command(self):
152+
"""Test that validate command is passed to core."""
153+
test_args = ["capiscio", "validate", "agent-card.json"]
154+
155+
with patch.object(sys, 'argv', test_args):
156+
with patch('capiscio.cli.run_core') as mock_run_core:
157+
with patch.object(sys, 'exit'):
158+
main()
159+
mock_run_core.assert_called_once_with(["validate", "agent-card.json"])
160+
161+
def test_score_command(self):
162+
"""Test that score command is passed to core."""
163+
test_args = ["capiscio", "score", "https://example.com/agent"]
164+
165+
with patch.object(sys, 'argv', test_args):
166+
with patch('capiscio.cli.run_core') as mock_run_core:
167+
with patch.object(sys, 'exit'):
168+
main()
169+
mock_run_core.assert_called_once_with(["score", "https://example.com/agent"])
170+
171+
def test_help_command(self):
172+
"""Test that help is passed to core."""
173+
test_args = ["capiscio", "--help"]
174+
175+
with patch.object(sys, 'argv', test_args):
176+
with patch('capiscio.cli.run_core') as mock_run_core:
177+
with patch.object(sys, 'exit'):
178+
main()
179+
mock_run_core.assert_called_once_with(["--help"])
180+
181+
def test_version_command(self):
182+
"""Test that --version (without wrapper prefix) is passed to core."""
183+
test_args = ["capiscio", "--version"]
184+
185+
with patch.object(sys, 'argv', test_args):
186+
with patch('capiscio.cli.run_core') as mock_run_core:
187+
with patch.object(sys, 'exit'):
188+
main()
189+
mock_run_core.assert_called_once_with(["--version"])
190+
191+
def test_complex_args(self):
192+
"""Test complex argument combinations are passed correctly."""
193+
test_args = [
194+
"capiscio", "validate",
195+
"--url", "https://example.com",
196+
"--output", "json",
197+
"--verbose",
198+
"--strict"
199+
]
200+
201+
with patch.object(sys, 'argv', test_args):
202+
with patch('capiscio.cli.run_core') as mock_run_core:
203+
with patch.object(sys, 'exit'):
204+
main()
205+
expected = [
206+
"validate",
207+
"--url", "https://example.com",
208+
"--output", "json",
209+
"--verbose",
210+
"--strict"
211+
]
212+
mock_run_core.assert_called_once_with(expected)
213+
214+
215+
class TestExitCodes:
216+
"""Tests for exit code handling."""
217+
218+
def test_run_core_exit_code_propagated(self):
219+
"""Test that run_core exit code is propagated."""
220+
test_args = ["capiscio", "validate", "nonexistent.json"]
221+
222+
with patch.object(sys, 'argv', test_args):
223+
with patch('capiscio.cli.run_core', return_value=1) as mock_run_core:
224+
with patch.object(sys, 'exit') as mock_exit:
225+
main()
226+
mock_exit.assert_called_with(1)
227+
228+
def test_run_core_success_exit_code(self):
229+
"""Test that successful run_core exit code is propagated."""
230+
test_args = ["capiscio", "validate", "valid.json"]
231+
232+
with patch.object(sys, 'argv', test_args):
233+
with patch('capiscio.cli.run_core', return_value=0):
234+
with patch.object(sys, 'exit') as mock_exit:
235+
main()
236+
mock_exit.assert_called_with(0)
237+

0 commit comments

Comments
 (0)