codemie-test-harness 0.1.128__py3-none-any.whl → 0.1.130__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.

Potentially problematic release.


This version of codemie-test-harness might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: codemie-test-harness
3
- Version: 0.1.128
3
+ Version: 0.1.130
4
4
  Summary: Autotest for CodeMie backend and UI
5
5
  Author: Anton Yeromin
6
6
  Author-email: anton_yeromin@epam.com
@@ -13,7 +13,7 @@ Requires-Dist: aws-assume-role-lib (>=2.10.0,<3.0.0)
13
13
  Requires-Dist: boto3 (>=1.39.8,<2.0.0)
14
14
  Requires-Dist: click (>=8.1.7,<9.0.0)
15
15
  Requires-Dist: codemie-plugins (>=0.1.123,<0.2.0)
16
- Requires-Dist: codemie-sdk-python (==0.1.128)
16
+ Requires-Dist: codemie-sdk-python (==0.1.130)
17
17
  Requires-Dist: pytest (>=8.4.1,<9.0.0)
18
18
  Requires-Dist: pytest-playwright (>=0.7.0,<0.8.0)
19
19
  Requires-Dist: pytest-reportportal (>=5.5.2,<6.0.0)
@@ -52,7 +52,7 @@ tests/assistant/tools/servicenow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
52
52
  tests/assistant/tools/servicenow/test_servicenow_tools.py,sha256=x3m5CW65la_FVwL5inJwl35KU_bhMVJfNsqTnyYnFkc,589
53
53
  tests/assistant/tools/vcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  tests/assistant/tools/vcs/test_assistant_with_vcs_tools.py,sha256=j9lWYbQ5BjU-4L74tBm4gxQqfb3TCc0T9SEFluTDytc,859
55
- tests/conftest.py,sha256=OJGJtMmjcecwXvborzxvBJfi2ASLAgsN-rEGZ_GmmRE,25568
55
+ tests/conftest.py,sha256=PdhMQMvYlhklKW4KuEda20HnzfO2eBoIsQ5AafCYd0Q,25719
56
56
  tests/e2e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  tests/e2e/test_e2e.py,sha256=_jWiDgUhOpO2p2VH987RXepS3PKvd1pSd3IFxKtK_z4,6215
58
58
  tests/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -69,6 +69,8 @@ tests/integrations/user/test_user_integrations.py,sha256=wmTYEK55y5aKU-DjOMyJnI_
69
69
  tests/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
70
  tests/llm/assistants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  tests/llm/assistants/test_llm.py,sha256=XjWZH7Pg8-cva64OlkZYWPMGZJ3MeCLJXsxlVnYXqWs,3162
72
+ tests/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
+ tests/providers/test_providers_endpoints.py,sha256=5A2JJvqsetcAP3IoqK4eoiebsdUC5rxr4gJ-sSFRq6M,7836
72
74
  tests/search/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
73
75
  tests/search/test_search_assistant.py,sha256=Pqbv9ysVy-IN4v05orBq0akpuHkzHDAtVa6eLorD5GE,3170
74
76
  tests/search/test_search_datasource.py,sha256=b_Q7zjCjw_9qjUQEblUnoZeDxXOMZLsXmWQnxuj2GQk,6618
@@ -108,6 +110,7 @@ tests/test_data/direct_tools/vcs_tools_test_data.py,sha256=ioMJ-eQfa2S1NPmXHyb9U
108
110
  tests/test_data/file_management_tools_test_data.py,sha256=TP9D2CtrZ_N6BFLcqErW6ggHlN9hUkSKGGGwo4Gt55c,3727
109
111
  tests/test_data/file_test_data.py,sha256=lhMJBbejENthkDNcg5vvUB-nWFU4zidnPaplQJBU1CM,11712
110
112
  tests/test_data/files/large-files/large_file.txt,sha256=uLjvYxR0RpLPrXstAfhvfWhMwiWrhsaCl_WV-Lh6GKA,110100480
113
+ tests/test_data/files/provider_payload.json,sha256=TkRb0GucodIXg3TgjxRD_mJOJRsQw_oFKv3pyTSYlZ0,17371
111
114
  tests/test_data/files/test.csv,sha256=M1FdTVc3LM74A2g6dQxRfOmJEvyJXRKBzWVzD4lFSno,52
112
115
  tests/test_data/files/test.docx,sha256=COrp_ujSoOVcG88in3t3w2oGckbd7xovk-ArsOrAq1w,26643
113
116
  tests/test_data/files/test.gif,sha256=y8UgoZSskETNHFBx5l7dQuoMXhbsPKpYX65kCAWRYI0,3442386
@@ -210,11 +213,12 @@ tests/utils/constants.py,sha256=pQB9dC2MgT8X1wPA0rWmGJn4HSWCpUZvvLuI42kw2nY,1090
210
213
  tests/utils/datasource_utils.py,sha256=Dwos1XAZefm1wjB_EkJ2S9Ax4a74MIDKaOxctGAdaH8,12443
211
214
  tests/utils/file_utils.py,sha256=hY-kwnyzvtd1BQif8r5NhvRTGfpKLmQKyRsq1Tuflhg,585
212
215
  tests/utils/gitbud_utils.py,sha256=UJ3RbhPSjHQSdos6S6zTR9iZULrBDJXoXq9cbjFH7bo,7829
213
- tests/utils/http_utils.py,sha256=qsKNvBVN6nrRlz0lZRYhhxdrI6Ecs17KUKo8rsrpJ_o,1598
216
+ tests/utils/http_utils.py,sha256=wjhttibzzNhleKzWgWC01Q0Y5sV9scu-Ski-qgJPd-Q,4179
214
217
  tests/utils/integration_utils.py,sha256=nP7pqDklkp_xPp4hUjSjZ_MBgeRucjYz1CrDq5Zuw58,4358
215
218
  tests/utils/json_utils.py,sha256=PWO4Ixxgta_zkdq-8umcP9qwDSi9JFxMuaT2NW3v1eI,226
216
219
  tests/utils/logger_util.py,sha256=6Kca4pLxyTYnUgm2i3j19DdZSH6XUSGXPjHtExx33QU,828
217
220
  tests/utils/notification_utils.py,sha256=0x0-yEIH7Azn5Cs2KRXHfQVBJwiwo1F8xSnfaynvQ8Q,3459
221
+ tests/utils/provider_utils.py,sha256=jxyDxiC1TKqnev1xtmsXLKNJbDqAG12kGDB1tuK_O1g,5077
218
222
  tests/utils/pytest_utils.py,sha256=k-mEjX2qpnh37sqKpJqYhZT6BV9974y_KaAhv8Xj9GI,284
219
223
  tests/utils/search_utils.py,sha256=O8uYyczObhTYtmdL6TVmH5F9xPuppICiGVbixSmRhzo,1108
220
224
  tests/utils/similarity_check.py,sha256=vUp1ksz80hZwTsykCc027bR3tCXwd3ZdRLmMtYnEjjg,1447
@@ -314,7 +318,7 @@ tests/workflow/virtual_assistant_tools/servicenow/__init__.py,sha256=47DEQpj8HBS
314
318
  tests/workflow/virtual_assistant_tools/servicenow/test_workflow_with_servicenow_tools.py,sha256=ecTfkwxPMbyyEKS-dArAQkluZURO1nThwDdD66mgC3E,814
315
319
  tests/workflow/virtual_assistant_tools/vcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
316
320
  tests/workflow/virtual_assistant_tools/vcs/test_workflow_with_vcs_tools.py,sha256=G0iI3X7SGEEy1Z2Hc7j917saY3lpzgfil_GtALCFilE,1043
317
- codemie_test_harness-0.1.128.dist-info/METADATA,sha256=_lnQLk4z8G70SdoyMeeqhBiIdQcXRGnDPYWLm2dzg9o,8998
318
- codemie_test_harness-0.1.128.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
319
- codemie_test_harness-0.1.128.dist-info/entry_points.txt,sha256=n98t-EOM5M1mnMl_j2X4siyeO9zr0WD9a5LF7JyElIM,73
320
- codemie_test_harness-0.1.128.dist-info/RECORD,,
321
+ codemie_test_harness-0.1.130.dist-info/METADATA,sha256=NO40P2WjyBMPyafu0tTS80UtDcwCIKDj9rmK69SMR9M,8998
322
+ codemie_test_harness-0.1.130.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
323
+ codemie_test_harness-0.1.130.dist-info/entry_points.txt,sha256=n98t-EOM5M1mnMl_j2X4siyeO9zr0WD9a5LF7JyElIM,73
324
+ codemie_test_harness-0.1.130.dist-info/RECORD,,
tests/conftest.py CHANGED
@@ -34,6 +34,7 @@ from tests.utils.gitbud_utils import GitBudUtils
34
34
  from tests.utils.integration_utils import IntegrationUtils
35
35
  from tests.utils.logger_util import setup_logger
36
36
  from tests.utils.notification_utils import GmailUtils
37
+ from tests.utils.provider_utils import ProviderUtils
37
38
  from tests.utils.search_utils import SearchUtils
38
39
  from tests.utils.similarity_check import SimilarityCheck
39
40
  from tests.utils.workflow_utils import WorkflowUtils
@@ -818,3 +819,6 @@ def pytest_sessionfinish(session):
818
819
  if prefix in workflow.name:
819
820
  client.workflows.delete(workflow_id=workflow.id)
820
821
  sleep(clean_up_timeout)
822
+
823
+ providers_utils = ProviderUtils(client)
824
+ providers_utils.cleanup_test_providers()
File without changes
@@ -0,0 +1,239 @@
1
+ import pytest
2
+ from hamcrest import (
3
+ assert_that,
4
+ is_,
5
+ equal_to,
6
+ has_key,
7
+ all_of,
8
+ )
9
+
10
+ from tests.utils.base_utils import get_random_name
11
+ from tests.utils.provider_utils import ProviderUtils
12
+
13
+
14
+ @pytest.fixture(scope="session")
15
+ def providers_utils(client):
16
+ return ProviderUtils(client)
17
+
18
+
19
+ nonexistent_id = "nonexistent-provider-id-12345"
20
+
21
+ # =============================================================================
22
+ # GET /v1/providers - List all providers
23
+ # =============================================================================
24
+
25
+
26
+ @pytest.mark.regression
27
+ def test_get_providers_endpoint(providers_utils):
28
+ response = providers_utils.send_get_request_to_providers_endpoint()
29
+
30
+ assert_that(response.status_code, equal_to(200))
31
+
32
+ response_data = response.json()
33
+
34
+ # Validate response structure
35
+ assert_that(isinstance(response_data, list), is_(True))
36
+ for provider in response_data:
37
+ assert_that(
38
+ provider,
39
+ all_of(has_key("provided_toolkits"), has_key("service_location_url")),
40
+ )
41
+
42
+
43
+ # =============================================================================
44
+ # POST /v1/providers - Create new provider
45
+ # =============================================================================
46
+
47
+
48
+ @pytest.mark.regression
49
+ def test_post_providers_endpoint(providers_utils):
50
+ request_json = providers_utils.provider_request_json()
51
+ # Ensure unique provider name to avoid conflicts
52
+ provider_name = get_random_name()
53
+ request_json["name"] = provider_name
54
+
55
+ create_response = providers_utils.send_post_request_to_providers_endpoint(
56
+ request_json
57
+ )
58
+
59
+ assert_that(create_response.status_code, equal_to(200))
60
+
61
+ # get provider to verify it was created
62
+ get_response = providers_utils.send_get_request_to_providers_endpoint()
63
+
64
+ providers = get_response.json()
65
+ created_provider = next((p for p in providers if p["name"] == provider_name), None)
66
+
67
+ assert_that(
68
+ created_provider is not None,
69
+ is_(True),
70
+ "Created provider should be in the list",
71
+ )
72
+
73
+
74
+ @pytest.mark.regression
75
+ def test_post_providers_endpoint_validation_error(providers_utils):
76
+ """Test POST /v1/providers with invalid provided_toolkits node"""
77
+ request_json = providers_utils.provider_request_json()
78
+ request_json["provided_toolkits"] = "invalid_value"
79
+
80
+ response = providers_utils.send_post_request_to_providers_endpoint(request_json)
81
+
82
+ assert_that(response.status_code, equal_to(422))
83
+ assert_that(
84
+ response.json()["error"]["details"][0]["msg"],
85
+ equal_to("Input should be a valid list"),
86
+ )
87
+
88
+
89
+ @pytest.mark.regression
90
+ def test_post_providers_endpoint_with_existing_name(providers_utils):
91
+ provider_id = providers_utils.create_provider()
92
+
93
+ provider = providers_utils.get_provider_by_id(provider_id).json()
94
+
95
+ existing_name = provider["name"]
96
+ request_json = providers_utils.provider_request_json()
97
+ request_json["name"] = existing_name
98
+
99
+ create_response = providers_utils.send_post_request_to_providers_endpoint(
100
+ request_json
101
+ )
102
+
103
+ assert_that(create_response.status_code, equal_to(409))
104
+ assert_that(
105
+ create_response.json()["error"]["details"],
106
+ equal_to(f"A provider with the name [{existing_name}] already exists."),
107
+ )
108
+
109
+
110
+ # =============================================================================
111
+ # GET /v1/providers/datasource_schemas - Get datasource schemas
112
+ # =============================================================================
113
+
114
+
115
+ @pytest.mark.regression
116
+ def test_get_providers_datasource_schema_endpoint(providers_utils):
117
+ response = providers_utils.send_get_request_datasource_schemas_endpoint()
118
+
119
+ assert_that(response.status_code, equal_to(200))
120
+
121
+ response_data = response.json()
122
+
123
+ # Validate response structure
124
+ assert_that(isinstance(response_data, list), is_(True))
125
+ for provider in response_data:
126
+ assert_that(
127
+ provider,
128
+ all_of(
129
+ has_key("id"),
130
+ has_key("provider_name"),
131
+ has_key("name"),
132
+ has_key("base_schema"),
133
+ has_key("create_schema"),
134
+ ),
135
+ )
136
+
137
+
138
+ # =============================================================================
139
+ # GET /v1/providers/{provider_id} - Get specific provider
140
+ # =============================================================================
141
+
142
+
143
+ @pytest.mark.regression
144
+ def test_get_provider_by_id_endpoint(providers_utils):
145
+ provider_id = providers_utils.create_provider()
146
+
147
+ provider_response = providers_utils.get_provider_by_id(provider_id)
148
+
149
+ assert_that(provider_response.status_code, equal_to(200))
150
+ assert_that(provider_response.json()["id"], equal_to(provider_id))
151
+
152
+
153
+ @pytest.mark.regression
154
+ def test_get_nonexistent_provider(providers_utils):
155
+ """Test error handling when requesting nonexistent provider."""
156
+ response = providers_utils.get_provider_by_id(nonexistent_id)
157
+
158
+ assert_that(response.status_code, equal_to(404))
159
+
160
+ assert_that(
161
+ response.json()["error"]["details"],
162
+ equal_to(
163
+ f"The provider with ID [{nonexistent_id}] could not be found in the system."
164
+ ),
165
+ )
166
+
167
+
168
+ # =============================================================================
169
+ # PUT /v1/providers/{provider_id} - Update specific provider
170
+ # =============================================================================
171
+
172
+
173
+ @pytest.mark.regression
174
+ def test_put_provider_endpoint(providers_utils):
175
+ provider_id = providers_utils.create_provider()
176
+ provider = providers_utils.get_provider_by_id(provider_id).json()
177
+
178
+ request_json = providers_utils.provider_request_json()
179
+ toolkits = provider["provided_toolkits"]
180
+ toolkits[0]["name"] = "Updated toolkit name"
181
+ toolkits[0]["description"] = "Updated toolkit description"
182
+ request_json["provided_toolkits"] = toolkits
183
+ request_json["name"] = f"{provider['name']} - updated"
184
+ update_response = providers_utils.update_provider(provider_id, request_json)
185
+
186
+ assert_that(update_response.status_code, equal_to(200))
187
+ assert_that(update_response.json()["provided_toolkits"], equal_to(toolkits))
188
+
189
+
190
+ @pytest.mark.regression
191
+ def test_put_nonexistent_provider(providers_utils):
192
+ response = providers_utils.update_provider(nonexistent_id, {})
193
+
194
+ assert_that(response.status_code, equal_to(404))
195
+ assert_that(
196
+ response.json()["error"]["details"],
197
+ equal_to(
198
+ f"The provider with ID [{nonexistent_id}] could not be found in the system."
199
+ ),
200
+ )
201
+
202
+
203
+ # =============================================================================
204
+ # DELETE /v1/providers/{provider_id} - Delete specific provider
205
+ # =============================================================================
206
+
207
+
208
+ @pytest.mark.regression
209
+ def test_delete_provider_endpoint(providers_utils):
210
+ provider_id = providers_utils.create_provider()
211
+ delete_response = providers_utils.send_delete_provider_request(provider_id)
212
+
213
+ assert_that(delete_response.status_code, equal_to(204))
214
+
215
+ response = providers_utils.get_provider_by_id(provider_id)
216
+
217
+ assert_that(response.status_code, equal_to(404))
218
+
219
+ assert_that(
220
+ response.json()["error"]["details"],
221
+ equal_to(
222
+ f"The provider with ID [{provider_id}] could not be found in the system."
223
+ ),
224
+ )
225
+
226
+
227
+ @pytest.mark.regression
228
+ def test_delete_nonexistent_provider(providers_utils):
229
+ """Test error handling when requesting nonexistent provider."""
230
+ response = providers_utils.send_delete_provider_request(nonexistent_id)
231
+
232
+ assert_that(response.status_code, equal_to(404))
233
+
234
+ assert_that(
235
+ response.json()["error"]["details"],
236
+ equal_to(
237
+ f"The provider with ID [{nonexistent_id}] could not be found in the system."
238
+ ),
239
+ )
@@ -0,0 +1,382 @@
1
+ {
2
+ "id":"3928ea28-e9e2-4382-aae0-09d5a24dc2am",
3
+ "date":"2025-04-02T12:54:19.693921",
4
+ "update_date":"2025-04-02T13:50:36.917335",
5
+ "name":"test",
6
+ "service_location_url":"https://codemie-preview.lab.epam.com/codemie-provider-stub-api/",
7
+ "configuration":{
8
+ "auth_type":"Bearer"
9
+ },
10
+ "provided_toolkits":[
11
+ {
12
+ "toolkit_id":"023953e9-b4b2-4264-aa24-7a20ab7cb201",
13
+ "name":"TestAutomationCodeAnalysesToolkit",
14
+ "description":"This ToolKit provides tools for indexing the source code of provided repository and set of methods to get insights of the code and code snippets.",
15
+ "toolkit_config":{
16
+ "type":"Code Analyses Datasource Configuration",
17
+ "description":"Configuration for connecting to GitHub repositories to access code for indexing and analysis.",
18
+ "parameters":{
19
+ "access_token":{
20
+ "description":"Github/Gitlab project access token with appropriate scopes for repository access",
21
+ "type":"Secret",
22
+ "required":true,
23
+ "enum":null
24
+ },
25
+ "datasource_id":{
26
+ "description":"Unique identifier for the datasource",
27
+ "type":"UUID",
28
+ "required":true,
29
+ "enum":null
30
+ },
31
+ "api_url":{
32
+ "description":"Git API URL (use your Git Enterprise API URL)",
33
+ "type":"URL",
34
+ "required":true,
35
+ "enum":null
36
+ },
37
+ "branch":{
38
+ "description":"Branch to index, defaults to master",
39
+ "type":"String",
40
+ "required":false,
41
+ "enum":null
42
+ }
43
+ }
44
+ },
45
+ "provided_tools":[
46
+ {
47
+ "name":"create_datasource",
48
+ "description":"Creates a new datasource by indexing a Git repository for code analysis. The indexing process extracts code structure, dependencies, and other metadata to enable subsequent queries.",
49
+ "args_schema":{
50
+ "exclude_glob":{
51
+ "type":"String",
52
+ "required":false,
53
+ "description":"Comma separated string of file paths to exclude from indexing",
54
+ "enum":null
55
+ },
56
+ "analyzer":{
57
+ "type":"List",
58
+ "required":false,
59
+ "description":"Type of code analyzer to use for the project. Supported analyzers Java/TS/JS/C#/C++. If not specified best matching analyzer will be chosen automatically.",
60
+ "enum":[
61
+ "Java",
62
+ "TS",
63
+ "JS",
64
+ "C#",
65
+ "C++",
66
+ "Other"
67
+ ]
68
+ },
69
+ "datasource_root":{
70
+ "type":"String",
71
+ "required":true,
72
+ "description":"Root directory of the project to be indexed",
73
+ "enum":null
74
+ }
75
+ },
76
+ "tool_metadata":{
77
+ "tool_type":"stateful",
78
+ "tool_purpose":"life_cycle_management",
79
+ "tool_action_type":"create"
80
+ },
81
+ "tool_result_type":"Json",
82
+ "sync_invocation_supported":false,
83
+ "async_invocation_supported":true
84
+ },
85
+ {
86
+ "name":"reindex_datasource",
87
+ "description":"Updates an existing datasource by re-indexing the associated Git repository to incorporate the latest changes and maintain synchronization with the current codebase.",
88
+ "args_schema":{
89
+
90
+ },
91
+ "tool_metadata":{
92
+ "tool_type":"stateful",
93
+ "tool_purpose":"life_cycle_management",
94
+ "tool_action_type":"modify"
95
+ },
96
+ "tool_result_type":"Json",
97
+ "sync_invocation_supported":false,
98
+ "async_invocation_supported":true
99
+ },
100
+ {
101
+ "name":"delete_datasource",
102
+ "description":"Removes an existing datasource and all its indexed data from the system, freeing up resources and cleaning up stale references.",
103
+ "args_schema":{
104
+
105
+ },
106
+ "tool_metadata":{
107
+ "tool_type":"stateful",
108
+ "tool_purpose":"life_cycle_management",
109
+ "tool_action_type":"remove"
110
+ },
111
+ "tool_result_type":"Json",
112
+ "sync_invocation_supported":false,
113
+ "async_invocation_supported":true
114
+ },
115
+ {
116
+ "name":"get_files_tree",
117
+ "description":"Retrieves a hierarchical representation of the file system structure of the indexed repository, showing directories and files in a tree format for easy navigation.",
118
+ "args_schema":{
119
+ "path":{
120
+ "type":"String",
121
+ "required":true,
122
+ "description":"Relative path within the repository to start the tree structure from",
123
+ "enum":null
124
+ },
125
+ "level":{
126
+ "type":"Number",
127
+ "required":false,
128
+ "description":"Maximum depth of the tree structure, defaults to full depth",
129
+ "enum":null
130
+ },
131
+ "limit":{
132
+ "type":"Number",
133
+ "required":false,
134
+ "description":"Maximum number of files to include in the tree structure, defaults to all files",
135
+ "enum":null
136
+ }
137
+ },
138
+ "tool_metadata":{
139
+ "tool_type":"stateful",
140
+ "tool_purpose":"data_retrieval",
141
+ "tool_action_type":null
142
+ },
143
+ "tool_result_type":"String",
144
+ "sync_invocation_supported":true,
145
+ "async_invocation_supported":false
146
+ },
147
+ {
148
+ "name":"get_files_list",
149
+ "description":"Retrieves a list of files and directories within the specified path in the indexed repository, showing file names and types for easy reference.",
150
+ "args_schema":{
151
+ "path":{
152
+ "type":"String",
153
+ "required":true,
154
+ "description":"Relative path within the repository to list files from",
155
+ "enum":null
156
+ },
157
+ "limit":{
158
+ "type":"Number",
159
+ "required":false,
160
+ "description":"Maximum number of files to include in the list, defaults to all files",
161
+ "enum":null
162
+ },
163
+ "recursive":{
164
+ "type":"Boolean",
165
+ "required":false,
166
+ "description":"Flag to include files from subdirectories recursively, defaults to false",
167
+ "enum":null
168
+ }
169
+ },
170
+ "tool_metadata":{
171
+ "tool_type":"stateful",
172
+ "tool_purpose":"data_retrieval",
173
+ "tool_action_type":null
174
+ },
175
+ "tool_result_type":"Json",
176
+ "sync_invocation_supported":true,
177
+ "async_invocation_supported":false
178
+ },
179
+ {
180
+ "name":"get_code_members",
181
+ "description":"Extracts and returns structured information about code components such as classes, methods, functions, and variables within specified file in the indexed repository.",
182
+ "args_schema":{
183
+ "file_path":{
184
+ "type":"String",
185
+ "required":true,
186
+ "description":"Path of the file to extract code members from",
187
+ "enum":null
188
+ },
189
+ "start_line":{
190
+ "type":"Number",
191
+ "required":false,
192
+ "description":"Starting line number to extract code members from, defaults to 1",
193
+ "enum":null
194
+ },
195
+ "end_line":{
196
+ "type":"Number",
197
+ "required":false,
198
+ "description":"Ending line number to extract code members till, defaults to end of file",
199
+ "enum":null
200
+ }
201
+ },
202
+ "tool_metadata":{
203
+ "tool_type":"stateful",
204
+ "tool_purpose":"data_retrieval",
205
+ "tool_action_type":null
206
+ },
207
+ "tool_result_type":"Json",
208
+ "sync_invocation_supported":false,
209
+ "async_invocation_supported":true
210
+ },
211
+ {
212
+ "name":"get_trimmed_code",
213
+ "description":"Retrieves a simplified version of code from specified files with non-essential elements (like comments and some whitespace) removed to focus on the core functionality.",
214
+ "args_schema":{
215
+ "file_path":{
216
+ "type":"String",
217
+ "required":true,
218
+ "description":"Path of the file to extract trimmed code from",
219
+ "enum":null
220
+ },
221
+ "exclude_private":{
222
+ "type":"Boolean",
223
+ "required":false,
224
+ "description":"Flag to exclude private members from the trimmed code, defaults to false",
225
+ "enum":null
226
+ },
227
+ "show_line_numbers":{
228
+ "type":"Boolean",
229
+ "required":false,
230
+ "description":"Flag to include line numbers in the trimmed code, defaults to false",
231
+ "enum":null
232
+ }
233
+ },
234
+ "tool_metadata":{
235
+ "tool_type":"stateful",
236
+ "tool_purpose":"data_retrieval",
237
+ "tool_action_type":null
238
+ },
239
+ "tool_result_type":"String",
240
+ "sync_invocation_supported":true,
241
+ "async_invocation_supported":false
242
+ },
243
+ {
244
+ "name":"get_code",
245
+ "description":"Fetches the complete, unmodified source code of specified files or code segments from the indexed repository, preserving all original formatting and comments.",
246
+ "args_schema":{
247
+ "file_path":{
248
+ "type":"String",
249
+ "required":true,
250
+ "description":"Path of the file to extract code from",
251
+ "enum":null
252
+ },
253
+ "start_line":{
254
+ "type":"Number",
255
+ "required":false,
256
+ "description":"Starting line number to extract code from, defaults to 1",
257
+ "enum":null
258
+ },
259
+ "show_line_numbers":{
260
+ "type":"Boolean",
261
+ "required":false,
262
+ "description":"Flag to include line numbers in the code, defaults to false",
263
+ "enum":null
264
+ },
265
+ "end_line":{
266
+ "type":"Number",
267
+ "required":false,
268
+ "description":"Ending line number to extract code till, defaults to end of file",
269
+ "enum":null
270
+ }
271
+ },
272
+ "tool_metadata":{
273
+ "tool_type":"stateful",
274
+ "tool_purpose":"data_retrieval",
275
+ "tool_action_type":null
276
+ },
277
+ "tool_result_type":"String",
278
+ "sync_invocation_supported":true,
279
+ "async_invocation_supported":false
280
+ },
281
+ {
282
+ "name":"get_metadata",
283
+ "description":"Retrieves metadata information about specified files in the indexed repository, including file size, line count, and other relevant details.",
284
+ "args_schema":{
285
+ "file_path":{
286
+ "type":"String",
287
+ "required":true,
288
+ "description":"Path of the file to extract code from",
289
+ "enum":null
290
+ }
291
+ },
292
+ "tool_metadata":{
293
+ "tool_type":"stateful",
294
+ "tool_purpose":"data_retrieval",
295
+ "tool_action_type":null
296
+ },
297
+ "tool_result_type":"String",
298
+ "sync_invocation_supported":true,
299
+ "async_invocation_supported":false
300
+ },
301
+ {
302
+ "name":"get_outgoing_dependencies",
303
+ "description":"Analyzes and returns information about external dependencies and imports used by specified files or code components, helping to understand code relationships and dependency chains.",
304
+ "args_schema":{
305
+ "file_path":{
306
+ "type":"String",
307
+ "required":true,
308
+ "description":"Path of the file to get outgoing dependencies from",
309
+ "enum":null
310
+ },
311
+ "start_line":{
312
+ "type":"Number",
313
+ "required":false,
314
+ "description":"Starting line number to get outgoing dependencies from, defaults to 1",
315
+ "enum":null
316
+ },
317
+ "end_line":{
318
+ "type":"Number",
319
+ "required":false,
320
+ "description":"Ending line number to get outgoing dependencies till, defaults to end of file",
321
+ "enum":null
322
+ }
323
+ },
324
+ "tool_metadata":{
325
+ "tool_type":"stateful",
326
+ "tool_purpose":"data_retrieval",
327
+ "tool_action_type":null
328
+ },
329
+ "tool_result_type":"Json",
330
+ "sync_invocation_supported":true,
331
+ "async_invocation_supported":false
332
+ }
333
+ ]
334
+ },
335
+ {
336
+ "toolkit_id":"98212b72-8b83-4d70-852f-49f0df28a0d4",
337
+ "name":"TestToolkit",
338
+ "description":"Tst toolkit",
339
+ "toolkit_config":{
340
+ "type":"test toolkit",
341
+ "description":"Configuration for connecting to GitHub repositories to access code for indexing and analysis.",
342
+ "parameters":{
343
+
344
+ }
345
+ },
346
+ "provided_tools":[
347
+ {
348
+ "name":"test_tool",
349
+ "description":"Some test tools",
350
+ "args_schema":{
351
+ "service_url":{
352
+ "type":"String",
353
+ "required":true,
354
+ "description":"The url of the service",
355
+ "enum":null
356
+ },
357
+ "exclude_glob":{
358
+ "type":"String",
359
+ "required":false,
360
+ "description":"Comma separated string of file paths to exclude from indexing",
361
+ "enum":null
362
+ },
363
+ "service_method":{
364
+ "type":"String",
365
+ "required":true,
366
+ "description":"The token to access the service",
367
+ "enum":null
368
+ }
369
+ },
370
+ "tool_metadata":{
371
+ "tool_type":null,
372
+ "tool_purpose":null,
373
+ "tool_action_type":null
374
+ },
375
+ "tool_result_type":"Json",
376
+ "sync_invocation_supported":false,
377
+ "async_invocation_supported":true
378
+ }
379
+ ]
380
+ }
381
+ ]
382
+ }
tests/utils/http_utils.py CHANGED
@@ -2,10 +2,9 @@ import logging
2
2
  from typing import TypeVar, Type, Optional, Any, Union, Dict, List
3
3
 
4
4
  import requests
5
- from pydantic import BaseModel
6
-
7
5
  from codemie_sdk.utils.http import ApiRequestHandler
8
6
  from codemie_sdk.utils.http import log_request
7
+ from pydantic import BaseModel
9
8
 
10
9
  T = TypeVar("T", bound=Union[BaseModel, List[BaseModel], dict])
11
10
 
@@ -19,7 +18,7 @@ class RequestHandler(ApiRequestHandler):
19
18
  def post(
20
19
  self,
21
20
  endpoint: str,
22
- response_model: Type[T],
21
+ response_model: Type[T] = None,
23
22
  json_data: Optional[Dict[str, Any]] = None,
24
23
  stream: bool = False,
25
24
  wrap_response: bool = True,
@@ -51,3 +50,92 @@ class RequestHandler(ApiRequestHandler):
51
50
  return response
52
51
 
53
52
  return self._parse_response(response, response_model, wrap_response)
53
+
54
+ @log_request
55
+ def get(
56
+ self,
57
+ endpoint: str,
58
+ response_model: Type[T] = None,
59
+ params: Optional[Dict[str, Any]] = None,
60
+ wrap_response: bool = True,
61
+ ) -> Union[T, requests.Response]:
62
+ """Makes a GET request and parses the response.
63
+
64
+ Args:
65
+ endpoint: API endpoint path
66
+ response_model: Pydantic model class or List[Model] for response
67
+ params: Query parameters
68
+ wrap_response: Whether response is wrapped in 'data' field
69
+
70
+ Returns:
71
+ Parsed response object or list of objects
72
+ """
73
+ if params:
74
+ logger.debug(f"Request params: {params}")
75
+ response = requests.get(
76
+ url=f"{self._base_url}{endpoint}",
77
+ headers=self._get_headers(),
78
+ params=params,
79
+ verify=self._verify_ssl,
80
+ )
81
+
82
+ if response_model:
83
+ return self._parse_response(response, response_model, wrap_response)
84
+
85
+ return response
86
+
87
+ @log_request
88
+ def put(
89
+ self,
90
+ endpoint: str,
91
+ json_data: Dict[str, Any],
92
+ response_model: Type[T] = None,
93
+ params: Optional[Dict[str, Any]] = None,
94
+ wrap_response: bool = True,
95
+ ) -> Union[T, requests.Response]:
96
+ """Makes a PUT request and parses the response.
97
+
98
+ Args:
99
+ endpoint: API endpoint path
100
+ response_model: Pydantic model class or List[Model] for response
101
+ json_data: JSON request body
102
+ params: Query parameters
103
+ wrap_response: Whether response is wrapped in 'data' field
104
+
105
+ Returns:
106
+ Parsed response object or list of objects
107
+ """
108
+ logger.debug(f"Request body: {json_data}")
109
+ response = requests.put(
110
+ url=f"{self._base_url}{endpoint}",
111
+ headers=self._get_headers(),
112
+ json=json_data,
113
+ params=params,
114
+ verify=self._verify_ssl,
115
+ )
116
+
117
+ if response_model:
118
+ return self._parse_response(response, response_model, wrap_response)
119
+
120
+ return response
121
+
122
+ @log_request
123
+ def delete(
124
+ self,
125
+ endpoint: str,
126
+ ) -> requests.Response:
127
+ """Makes a DELETE request and parses the response.
128
+
129
+ Args:
130
+ endpoint: API endpoint path
131
+
132
+ Returns:
133
+ response object
134
+ """
135
+ response = requests.delete(
136
+ url=f"{self._base_url}{endpoint}",
137
+ headers=self._get_headers(),
138
+ verify=self._verify_ssl,
139
+ )
140
+
141
+ return response
@@ -0,0 +1,178 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from typing import Optional, Dict, Any
5
+
6
+ import requests
7
+ from codemie_sdk import CodeMieClient
8
+
9
+ from tests import autotest_entity_prefix
10
+ from tests.utils import api_domain, verify_ssl
11
+ from tests.utils.base_utils import (
12
+ BaseUtils,
13
+ get_random_name,
14
+ )
15
+ from tests.utils.http_utils import RequestHandler
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ providers_endpoint = "/v1/providers"
20
+
21
+
22
+ class ProviderUtils(BaseUtils):
23
+ def __init__(self, client: CodeMieClient):
24
+ """Initialize the provides service."""
25
+
26
+ super().__init__(client)
27
+ self._api = RequestHandler(api_domain, self.client.token, verify_ssl)
28
+
29
+ @staticmethod
30
+ def provider_request_json():
31
+ """Load provider request JSON from file."""
32
+ payload_path = os.path.join(
33
+ os.path.dirname(__file__), "../test_data/files/provider_payload.json"
34
+ )
35
+ with open(payload_path, "r") as pyload_file:
36
+ request_json = json.load(pyload_file)
37
+
38
+ return request_json
39
+
40
+ def send_post_request_to_providers_endpoint(
41
+ self, request: Dict[str, Any]
42
+ ) -> requests.Response:
43
+ """
44
+ Send request to provider creation endpoint without raising error for response status codes.
45
+
46
+ Args:
47
+ request: The provider creation request
48
+
49
+ Returns:
50
+ Raw response from '/v1/providers' endpoint
51
+ """
52
+ return self._api.post(providers_endpoint, json_data=request, stream=True)
53
+
54
+ def send_get_request_to_providers_endpoint(
55
+ self,
56
+ ) -> requests.Response:
57
+ """
58
+ Send request to get providers endpoint.
59
+
60
+ Returns:
61
+ Raw response from '/v1/providers' endpoint
62
+ """
63
+ return self._api.get(providers_endpoint)
64
+
65
+ def send_get_request_datasource_schemas_endpoint(
66
+ self,
67
+ ) -> requests.Response:
68
+ """
69
+ Send request to get datasource_schemas endpoint.
70
+
71
+ Returns:
72
+ Raw response from '/v1/providers/datasource_schemas' endpoint
73
+ """
74
+ return self._api.get(f"{providers_endpoint}/datasource_schemas")
75
+
76
+ def create_provider(
77
+ self,
78
+ ) -> str:
79
+ """
80
+ Creates a provider for test.
81
+
82
+ Returns:
83
+ Created provider id
84
+ """
85
+ request_json = self.provider_request_json()
86
+ # Ensure unique provider name to avoid conflicts
87
+ request_json["name"] = get_random_name()
88
+
89
+ response = self.send_post_request_to_providers_endpoint(request_json)
90
+
91
+ return response.json()["id"]
92
+
93
+ def list_providers(
94
+ self,
95
+ ) -> requests.Response:
96
+ """
97
+ List all providers.
98
+
99
+ Returns:
100
+ List of provider objects
101
+ """
102
+ response = self.send_get_request_to_providers_endpoint()
103
+
104
+ return response
105
+
106
+ def get_provider_by_id(self, provider_id: str) -> requests.Response:
107
+ """
108
+ Get a specific provider by ID.
109
+
110
+ Args:
111
+ provider_id: The provider ID
112
+
113
+ Returns:
114
+ Provider object
115
+ """
116
+ response = self._api.get(f"{providers_endpoint}/{provider_id}")
117
+
118
+ return response
119
+
120
+ def update_provider(
121
+ self,
122
+ provider_id: str,
123
+ provider_update_payload: dict,
124
+ ) -> requests.Response:
125
+ """
126
+ Update an existing provider.
127
+
128
+ Args:
129
+ provider_id: The provider ID to update
130
+ provider_update_payload: request body
131
+
132
+ Returns:
133
+ Updated provider object
134
+ """
135
+ endpoint = f"{providers_endpoint}/{provider_id}"
136
+
137
+ return self._api.put(endpoint, json_data=provider_update_payload)
138
+
139
+ def send_delete_provider_request(self, provider_id: str) -> requests.Response:
140
+ """
141
+ Delete a provider by ID.
142
+
143
+ Args:
144
+ provider_id: The provider ID to delete
145
+
146
+ Returns:
147
+ Response from delete operation
148
+ """
149
+ return self._api.delete(f"{providers_endpoint}/{provider_id}")
150
+
151
+ def cleanup_test_providers(self, name_prefix: Optional[str] = None):
152
+ """
153
+ Clean up test providers (those with autotest prefix or specified prefix).
154
+
155
+ Args:
156
+ name_prefix: Prefix to match for cleanup (uses autotest prefix if None)
157
+ """
158
+
159
+ prefix = name_prefix if name_prefix else autotest_entity_prefix
160
+
161
+ try:
162
+ providers = self.list_providers()
163
+ test_providers = [
164
+ provider
165
+ for provider in providers.json()
166
+ if provider["name"].startswith(prefix)
167
+ ]
168
+
169
+ for provider in test_providers:
170
+ try:
171
+ self.send_delete_provider_request(provider["id"])
172
+ except Exception as e:
173
+ # Log but don't fail cleanup for individual provider
174
+ logger.error(f"Failed to delete provider {provider['name']}: {e}")
175
+
176
+ except Exception as e:
177
+ # Log but don't fail if cleanup encounters issues
178
+ logger.error(f"Provider cleanup encountered an error: {e}")