cloudsmith-cli 1.11.0__py2.py3-none-any.whl → 1.11.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,357 +0,0 @@
1
- from unittest.mock import patch
2
-
3
- import cloudsmith_api
4
-
5
- from ....cli.commands.mcp import list_groups, list_tools
6
- from ....core.mcp.data import OpenAPITool
7
- from ....core.mcp.server import DynamicMCPServer
8
-
9
-
10
- class TestMCPListToolsCommand:
11
- def test_list_tools_command_basic(self, runner):
12
- """Test that list_tools command returns available tools."""
13
- # Mock tools data
14
- mock_tools = {
15
- "repos_list": OpenAPITool(
16
- name="repos_list",
17
- description="List repositories",
18
- method="GET",
19
- path="/repos/",
20
- parameters={"type": "object", "properties": {}, "required": []},
21
- base_url="https://api.cloudsmith.io",
22
- query_filter=None,
23
- is_destructive=False,
24
- is_read_only=True,
25
- ),
26
- "packages_list": OpenAPITool(
27
- name="packages_list",
28
- description="List packages",
29
- method="GET",
30
- path="/packages/",
31
- parameters={"type": "object", "properties": {}, "required": []},
32
- base_url="https://api.cloudsmith.io",
33
- query_filter=None,
34
- is_destructive=False,
35
- is_read_only=True,
36
- ),
37
- }
38
-
39
- with patch(
40
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_tools"
41
- ) as list_tools_mock:
42
- list_tools_mock.return_value = mock_tools
43
- result = runner.invoke(list_tools, catch_exceptions=False)
44
-
45
- assert result.exit_code == 0
46
- assert "repos_list" in result.output
47
- assert "packages_list" in result.output
48
- assert "List repositories" in result.output
49
- assert "List packages" in result.output
50
- list_tools_mock.assert_called_once()
51
-
52
- def test_list_tools_command_with_filtering(self, runner):
53
- """Test that list_tools command respects filtering configuration."""
54
- # Mock tools data - simulating filtered results
55
- mock_tools = {
56
- "repos_list": OpenAPITool(
57
- name="repos_list",
58
- description="List repositories",
59
- method="GET",
60
- path="/repos/",
61
- parameters={"type": "object", "properties": {}, "required": []},
62
- base_url="https://api.cloudsmith.io",
63
- query_filter=None,
64
- is_destructive=False,
65
- is_read_only=True,
66
- ),
67
- }
68
-
69
- with patch(
70
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_tools"
71
- ) as list_tools_mock:
72
- list_tools_mock.return_value = mock_tools
73
- result = runner.invoke(list_tools, catch_exceptions=False)
74
-
75
- assert result.exit_code == 0
76
- assert "repos_list" in result.output
77
- # Verify that filtered tools are not in the output
78
- assert "packages_list" not in result.output
79
- list_tools_mock.assert_called_once()
80
-
81
- def test_list_tools_command_json_output(self, runner):
82
- """Test that list_tools command can output JSON format."""
83
- mock_tools = {
84
- "repos_list": OpenAPITool(
85
- name="repos_list",
86
- description="List repositories",
87
- method="GET",
88
- path="/repos/",
89
- parameters={"type": "object", "properties": {}, "required": []},
90
- base_url="https://api.cloudsmith.io",
91
- query_filter=None,
92
- is_destructive=False,
93
- is_read_only=True,
94
- ),
95
- }
96
-
97
- with patch(
98
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_tools"
99
- ) as list_tools_mock:
100
- list_tools_mock.return_value = mock_tools
101
- result = runner.invoke(
102
- list_tools, ["--output-format", "json"], catch_exceptions=False
103
- )
104
-
105
- assert result.exit_code == 0
106
- # JSON output should contain structured data
107
- assert '"name":' in result.output or '"name": "repos_list"' in result.output
108
- list_tools_mock.assert_called_once()
109
-
110
- def test_list_tools_command_empty(self, runner):
111
- """Test that list_tools command handles empty tool list."""
112
- mock_tools = {}
113
-
114
- with patch(
115
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_tools"
116
- ) as list_tools_mock:
117
- list_tools_mock.return_value = mock_tools
118
- result = runner.invoke(list_tools, catch_exceptions=False)
119
-
120
- assert result.exit_code == 0
121
- assert "0 tools visible" in result.output
122
- list_tools_mock.assert_called_once()
123
-
124
-
125
- class TestMCPListGroupsCommand:
126
- def test_list_groups_command_basic(self, runner):
127
- """Test that list_groups command returns available tool groups."""
128
- # Mock groups data
129
- mock_groups = {
130
- "repos": ["repos_list", "repos_create", "repos_delete"],
131
- "packages": ["packages_list", "packages_read", "packages_delete"],
132
- "orgs": ["orgs_list", "orgs_read"],
133
- }
134
-
135
- with patch(
136
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_groups"
137
- ) as list_groups_mock:
138
- list_groups_mock.return_value = mock_groups
139
- result = runner.invoke(list_groups, catch_exceptions=False)
140
-
141
- assert result.exit_code == 0
142
- assert "repos" in result.output
143
- assert "packages" in result.output
144
- assert "orgs" in result.output
145
- # Check tool counts are displayed
146
- assert "3" in result.output # repos has 3 tools
147
- assert "2" in result.output # orgs has 2 tools
148
- list_groups_mock.assert_called_once()
149
-
150
- def test_list_groups_command_with_filtering(self, runner):
151
- """Test that list_groups command respects filtering configuration."""
152
- # Mock groups data - simulating filtered results with only repos group
153
- mock_groups = {
154
- "repos": ["repos_list", "repos_create"],
155
- }
156
-
157
- with patch(
158
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_groups"
159
- ) as list_groups_mock:
160
- list_groups_mock.return_value = mock_groups
161
- result = runner.invoke(list_groups, catch_exceptions=False)
162
-
163
- assert result.exit_code == 0
164
- assert "repos" in result.output
165
- # Verify that filtered groups are not in the output
166
- assert "packages" not in result.output
167
- assert "orgs" not in result.output
168
- list_groups_mock.assert_called_once()
169
-
170
- def test_list_groups_command_json_output(self, runner):
171
- """Test that list_groups command can output JSON format."""
172
- mock_groups = {
173
- "repos": ["repos_list", "repos_create"],
174
- "packages": ["packages_list"],
175
- }
176
-
177
- with patch(
178
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_groups"
179
- ) as list_groups_mock:
180
- list_groups_mock.return_value = mock_groups
181
- result = runner.invoke(
182
- list_groups, ["--output-format", "json"], catch_exceptions=False
183
- )
184
-
185
- assert result.exit_code == 0
186
- # JSON output should contain structured data
187
- assert '"name":' in result.output or '"name": "repos"' in result.output
188
- assert '"tools":' in result.output
189
- list_groups_mock.assert_called_once()
190
-
191
- def test_list_groups_command_empty(self, runner):
192
- """Test that list_groups command handles empty group list."""
193
- mock_groups = {}
194
-
195
- with patch(
196
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_groups"
197
- ) as list_groups_mock:
198
- list_groups_mock.return_value = mock_groups
199
- result = runner.invoke(list_groups, catch_exceptions=False)
200
-
201
- assert result.exit_code == 0
202
- assert "0 groups visible" in result.output
203
- list_groups_mock.assert_called_once()
204
-
205
- def test_list_groups_command_with_many_tools(self, runner):
206
- """Test that list_groups command shows sample tools for groups with many tools."""
207
- # Mock a group with more than 3 tools to test the "... (+N more)" feature
208
- mock_groups = {
209
- "repos": [
210
- "repos_list",
211
- "repos_create",
212
- "repos_read",
213
- "repos_update",
214
- "repos_delete",
215
- ],
216
- }
217
-
218
- with patch(
219
- "cloudsmith_cli.core.mcp.server.DynamicMCPServer.list_groups"
220
- ) as list_groups_mock:
221
- list_groups_mock.return_value = mock_groups
222
- result = runner.invoke(list_groups, catch_exceptions=False)
223
-
224
- assert result.exit_code == 0
225
- assert "repos" in result.output
226
- assert "5" in result.output # Total count of tools
227
- # Should show "... (+2 more)" since it only displays first 3
228
- assert "+2 more" in result.output
229
- list_groups_mock.assert_called_once()
230
-
231
-
232
- class TestMCPServerDynamicToolGeneration:
233
- def test_server_generates_tools_from_openapi_spec(self):
234
- """Test that MCP server dynamically creates tools from OpenAPI spec."""
235
- # Create a minimal OpenAPI spec
236
- mock_openapi_spec = {
237
- "paths": {
238
- "/repos/": {
239
- "get": {
240
- "operationId": "repos_list",
241
- "summary": "List all repositories",
242
- "parameters": [
243
- {
244
- "name": "page",
245
- "in": "query",
246
- "type": "integer",
247
- "description": "Page number",
248
- }
249
- ],
250
- }
251
- },
252
- "/repos/{owner}/{identifier}/": {
253
- "parameters": [
254
- {
255
- "name": "owner",
256
- "in": "path",
257
- "required": True,
258
- "type": "string",
259
- },
260
- {
261
- "name": "identifier",
262
- "in": "path",
263
- "required": True,
264
- "type": "string",
265
- },
266
- ],
267
- "get": {
268
- "operationId": "repos_read",
269
- "summary": "Get a specific repository",
270
- },
271
- "delete": {
272
- "operationId": "repos_delete",
273
- "summary": "Delete a repository",
274
- },
275
- },
276
- }
277
- }
278
-
279
- # Create API config
280
- api_config = cloudsmith_api.Configuration()
281
- api_config.host = "https://api.cloudsmith.io"
282
- api_config.api_key = {"X-Api-Key": "test-key"}
283
-
284
- # Create MCP server instance
285
- server = DynamicMCPServer(api_config=api_config, force_all_tools=True)
286
-
287
- # Mock the spec loading directly
288
- server.spec = mock_openapi_spec
289
-
290
- # Call the synchronous tool generation method
291
- import asyncio
292
-
293
- asyncio.run(
294
- server._generate_tools_from_spec() # pylint: disable=protected-access
295
- )
296
-
297
- # Verify tools were created
298
- assert len(server.tools) == 3
299
- assert "repos_list" in server.tools
300
- assert "repos_read" in server.tools
301
- assert "repos_delete" in server.tools
302
-
303
- # Verify tool details
304
- repos_list_tool = server.tools["repos_list"]
305
- assert repos_list_tool.name == "repos_list"
306
- assert repos_list_tool.description == "List all repositories"
307
- assert repos_list_tool.method == "GET"
308
- assert repos_list_tool.path == "/repos/"
309
- assert repos_list_tool.is_read_only is True
310
- assert repos_list_tool.is_destructive is False
311
-
312
- # Verify delete tool is marked as destructive
313
- repos_delete_tool = server.tools["repos_delete"]
314
- assert repos_delete_tool.is_destructive is True
315
- assert repos_delete_tool.is_read_only is False
316
-
317
- def test_server_respects_tool_filtering(self):
318
- """Test that MCP server filters tools based on configuration."""
319
- # Create a simple OpenAPI spec
320
- mock_openapi_spec = {
321
- "paths": {
322
- "/repos/": {
323
- "get": {
324
- "operationId": "repos_list",
325
- "summary": "List repositories",
326
- }
327
- },
328
- "/packages/": {
329
- "get": {
330
- "operationId": "packages_list",
331
- "summary": "List packages",
332
- }
333
- },
334
- }
335
- }
336
-
337
- api_config = cloudsmith_api.Configuration()
338
- api_config.host = "https://api.cloudsmith.io"
339
- api_config.api_key = {"X-Api-Key": "test-key"}
340
-
341
- # Create server with filtering - only allow repos group
342
- server = DynamicMCPServer(api_config=api_config, allowed_tool_groups=["repos"])
343
-
344
- # Mock the spec loading directly
345
- server.spec = mock_openapi_spec
346
-
347
- # Call the tool generation method
348
- import asyncio
349
-
350
- asyncio.run(
351
- server._generate_tools_from_spec() # pylint: disable=protected-access
352
- )
353
-
354
- # Verify only repos tools were created
355
- assert len(server.tools) == 1
356
- assert "repos_list" in server.tools
357
- assert "packages_list" not in server.tools
File without changes
@@ -1,17 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Any, Dict, Optional
3
-
4
-
5
- @dataclass
6
- class OpenAPITool:
7
- """Represents a tool generated from OpenAPI spec"""
8
-
9
- name: str
10
- description: str
11
- method: str
12
- path: str
13
- parameters: Dict[str, Any]
14
- base_url: str
15
- query_filter: Optional[str]
16
- is_destructive: bool = False
17
- is_read_only: bool = False