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

@@ -0,0 +1,418 @@
1
+ """Utility class for managing vendor workflows (AWS Bedrock, Azure, GCP)."""
2
+
3
+ from typing import Optional, List
4
+ from codemie_sdk.models.vendor_assistant import VendorType
5
+ from codemie_sdk.models.vendor_workflow import (
6
+ VendorWorkflowSettingsResponse,
7
+ VendorWorkflowsResponse,
8
+ VendorWorkflow,
9
+ VendorWorkflowAliasesResponse,
10
+ VendorWorkflowInstallRequest,
11
+ VendorWorkflowInstallResponse,
12
+ VendorWorkflowUninstallResponse,
13
+ VendorWorkflowStatus,
14
+ )
15
+ from codemie_test_harness.tests.utils.client_factory import get_client
16
+ from codemie_test_harness.tests.utils.logger_util import setup_logger
17
+
18
+ logger = setup_logger(__name__)
19
+
20
+ DRAFT_VERSION = "DRAFT"
21
+
22
+
23
+ class VendorWorkflowUtils:
24
+ """Utility class for vendor workflow operations."""
25
+
26
+ def __init__(self):
27
+ """Initialize VendorWorkflowUtils with CodeMie client."""
28
+ self.client = get_client()
29
+ self.vendor_workflow_service = self.client.vendor_workflows
30
+
31
+ def get_workflow_settings(
32
+ self,
33
+ vendor: VendorType,
34
+ page: int = 0,
35
+ per_page: int = 10,
36
+ ) -> VendorWorkflowSettingsResponse:
37
+ """Get workflow settings for a specific cloud vendor.
38
+
39
+ Args:
40
+ vendor: Cloud vendor type (AWS, AZURE, GCP)
41
+ page: Page number for pagination
42
+ per_page: Number of items per page
43
+
44
+ Returns:
45
+ VendorWorkflowSettingsResponse containing list of settings
46
+ """
47
+ logger.info(
48
+ f"Getting workflow settings for {vendor.value} (page={page}, per_page={per_page})"
49
+ )
50
+ settings = self.vendor_workflow_service.get_workflow_settings(
51
+ vendor=vendor, page=page, per_page=per_page
52
+ )
53
+ logger.info(
54
+ f"Retrieved {len(settings.data)} settings for {vendor.value} (total: {settings.pagination.total})"
55
+ )
56
+ return settings
57
+
58
+ def get_workflows(
59
+ self,
60
+ vendor: VendorType,
61
+ setting_id: str,
62
+ per_page: int = 10,
63
+ next_token: Optional[str] = None,
64
+ ) -> VendorWorkflowsResponse:
65
+ """Get workflows for a specific vendor setting.
66
+
67
+ Args:
68
+ vendor: Cloud vendor type
69
+ setting_id: ID of the vendor setting
70
+ per_page: Number of items per page
71
+ next_token: Token for pagination
72
+
73
+ Returns:
74
+ VendorWorkflowsResponse containing list of workflows
75
+ """
76
+ logger.info(
77
+ f"Getting workflows for {vendor.value} setting {setting_id} (per_page={per_page})"
78
+ )
79
+ workflows = self.vendor_workflow_service.get_workflows(
80
+ vendor=vendor,
81
+ setting_id=setting_id,
82
+ per_page=per_page,
83
+ next_token=next_token,
84
+ )
85
+ logger.info(
86
+ f"Retrieved {len(workflows.data)} workflows for setting {setting_id}"
87
+ )
88
+ return workflows
89
+
90
+ def get_workflow(
91
+ self,
92
+ vendor: VendorType,
93
+ workflow_id: str,
94
+ setting_id: str,
95
+ ) -> VendorWorkflow:
96
+ """Get a specific workflow by ID.
97
+
98
+ Args:
99
+ vendor: Cloud vendor type
100
+ workflow_id: ID of the workflow
101
+ setting_id: ID of the vendor setting
102
+
103
+ Returns:
104
+ VendorWorkflow with workflow details
105
+ """
106
+ logger.info(
107
+ f"Getting workflow {workflow_id} for {vendor.value} setting {setting_id}"
108
+ )
109
+ workflow = self.vendor_workflow_service.get_workflow(
110
+ vendor=vendor, workflow_id=workflow_id, setting_id=setting_id
111
+ )
112
+ logger.info(f"Retrieved workflow: {workflow.name} (status: {workflow.status})")
113
+ return workflow
114
+
115
+ def get_workflow_aliases(
116
+ self,
117
+ vendor: VendorType,
118
+ workflow_id: str,
119
+ setting_id: str,
120
+ per_page: int = 10,
121
+ next_token: Optional[str] = None,
122
+ ) -> VendorWorkflowAliasesResponse:
123
+ """Get aliases for a specific vendor workflow.
124
+
125
+ Args:
126
+ vendor: Cloud vendor type
127
+ workflow_id: ID of the workflow
128
+ setting_id: ID of the vendor setting
129
+ per_page: Number of items per page
130
+ next_token: Token for pagination
131
+
132
+ Returns:
133
+ VendorWorkflowAliasesResponse containing list of aliases
134
+ """
135
+ logger.info(
136
+ f"Getting aliases for workflow {workflow_id} in {vendor.value} setting {setting_id}"
137
+ )
138
+ aliases = self.vendor_workflow_service.get_workflow_aliases(
139
+ vendor=vendor,
140
+ workflow_id=workflow_id,
141
+ setting_id=setting_id,
142
+ per_page=per_page,
143
+ next_token=next_token,
144
+ )
145
+ logger.info(f"Retrieved {len(aliases.data)} aliases for workflow {workflow_id}")
146
+ return aliases
147
+
148
+ def install_workflows(
149
+ self,
150
+ vendor: VendorType,
151
+ workflows: List[VendorWorkflowInstallRequest],
152
+ ) -> VendorWorkflowInstallResponse:
153
+ """Install/activate vendor workflows.
154
+
155
+ Args:
156
+ vendor: Cloud vendor type
157
+ workflows: List of workflow installation requests
158
+
159
+ Returns:
160
+ VendorWorkflowInstallResponse containing installation summary with AI run IDs
161
+ """
162
+ logger.info(f"Installing {len(workflows)} workflow(s) for {vendor.value}")
163
+ response = self.vendor_workflow_service.install_workflows(
164
+ vendor=vendor, workflows=workflows
165
+ )
166
+ for item in response.summary:
167
+ logger.info(
168
+ f"Installed workflow {item.flowId} (alias: {item.flowAliasId}) -> CodeMie ID: {item.aiRunId}"
169
+ )
170
+ return response
171
+
172
+ def uninstall_workflow(
173
+ self,
174
+ vendor: VendorType,
175
+ codemie_id: str,
176
+ ) -> VendorWorkflowUninstallResponse:
177
+ """Uninstall/deactivate a vendor workflow.
178
+
179
+ Args:
180
+ vendor: Cloud vendor type
181
+ codemie_id: CodeMie workflow ID from installation
182
+
183
+ Returns:
184
+ VendorWorkflowUninstallResponse with success status
185
+ """
186
+ logger.info(f"Uninstalling workflow with CodeMie ID: {codemie_id}")
187
+ response = self.vendor_workflow_service.uninstall_workflow(
188
+ vendor=vendor, ai_run_id=codemie_id
189
+ )
190
+ if response.success:
191
+ logger.info(f"Successfully uninstalled workflow {codemie_id}")
192
+ else:
193
+ logger.warning(f"Failed to uninstall workflow {codemie_id}")
194
+ return response
195
+
196
+ def get_prepared_workflow(
197
+ self,
198
+ workflows: List[VendorWorkflow],
199
+ ) -> Optional[VendorWorkflow]:
200
+ """Get first PREPARED workflow from the list.
201
+
202
+ Args:
203
+ workflows: List of vendor workflows
204
+
205
+ Returns:
206
+ First PREPARED workflow or first workflow if none are PREPARED
207
+ """
208
+ return next(
209
+ (w for w in workflows if w.status == VendorWorkflowStatus.PREPARED),
210
+ workflows[0] if workflows else None,
211
+ )
212
+
213
+ def get_non_draft_alias(
214
+ self,
215
+ aliases: List,
216
+ ) -> Optional:
217
+ """Get first non-DRAFT alias from the list.
218
+
219
+ Args:
220
+ aliases: List of vendor workflow aliases
221
+
222
+ Returns:
223
+ First non-DRAFT alias or first alias if all are DRAFT
224
+ """
225
+ return next(
226
+ (a for a in aliases if a.version != DRAFT_VERSION),
227
+ aliases[0] if aliases else None,
228
+ )
229
+
230
+ def find_workflow_by_name(
231
+ self,
232
+ vendor: VendorType,
233
+ setting_id: str,
234
+ workflow_name: str,
235
+ ) -> Optional[tuple[VendorWorkflow, str]]:
236
+ """Find a workflow by name and return it with an alias.
237
+
238
+ Args:
239
+ vendor: Cloud vendor type
240
+ setting_id: ID of the vendor setting
241
+ workflow_name: Name of the workflow to find
242
+
243
+ Returns:
244
+ Tuple of (VendorWorkflow, alias_id) or None if workflow not found
245
+ """
246
+ logger.info(
247
+ f"Searching for workflow '{workflow_name}' in {vendor.value} setting {setting_id}"
248
+ )
249
+ workflows_response = self.get_workflows(vendor=vendor, setting_id=setting_id)
250
+
251
+ for workflow in workflows_response.data:
252
+ if (
253
+ workflow.name == workflow_name
254
+ and workflow.status == VendorWorkflowStatus.PREPARED
255
+ ):
256
+ aliases_response = self.get_workflow_aliases(
257
+ vendor=vendor, workflow_id=workflow.id, setting_id=setting_id
258
+ )
259
+ if aliases_response.data:
260
+ non_draft_alias = self.get_non_draft_alias(aliases_response.data)
261
+ if non_draft_alias:
262
+ logger.info(
263
+ f"Found workflow: {workflow.name} (ID: {workflow.id}, Alias: {non_draft_alias.id})"
264
+ )
265
+ return workflow, non_draft_alias.id
266
+
267
+ logger.warning(
268
+ f"Workflow '{workflow_name}' not found for {vendor.value} setting {setting_id}"
269
+ )
270
+ return None
271
+
272
+ def find_first_available_workflow(
273
+ self,
274
+ vendor: VendorType,
275
+ setting_id: str,
276
+ ) -> Optional[tuple[VendorWorkflow, str]]:
277
+ """Find the first available (PREPARED) workflow with an alias.
278
+
279
+ Args:
280
+ vendor: Cloud vendor type
281
+ setting_id: ID of the vendor setting
282
+
283
+ Returns:
284
+ Tuple of (VendorWorkflow, alias_id) or None if no available workflow found
285
+ """
286
+ logger.info(
287
+ f"Searching for available workflow in {vendor.value} setting {setting_id}"
288
+ )
289
+ workflows_response = self.get_workflows(vendor=vendor, setting_id=setting_id)
290
+
291
+ for workflow in workflows_response.data:
292
+ if workflow.status == VendorWorkflowStatus.PREPARED:
293
+ aliases_response = self.get_workflow_aliases(
294
+ vendor=vendor, workflow_id=workflow.id, setting_id=setting_id
295
+ )
296
+ if aliases_response.data:
297
+ first_alias = aliases_response.data[0]
298
+ logger.info(
299
+ f"Found available workflow: {workflow.name} (ID: {workflow.id}, Alias: {first_alias.id})"
300
+ )
301
+ return workflow, first_alias.id
302
+
303
+ logger.warning(
304
+ f"No available workflow found for {vendor.value} setting {setting_id}"
305
+ )
306
+ return None
307
+
308
+ def install_workflow_by_name(
309
+ self,
310
+ vendor: VendorType,
311
+ setting_id: str,
312
+ workflow_name: str,
313
+ ) -> Optional[str]:
314
+ """Find and install a workflow by name.
315
+
316
+ Args:
317
+ vendor: Cloud vendor type
318
+ setting_id: ID of the vendor setting
319
+ workflow_name: Name of the workflow to install
320
+
321
+ Returns:
322
+ CodeMie ID of the installed workflow or None if workflow not found
323
+ """
324
+ result = self.find_workflow_by_name(
325
+ vendor=vendor, setting_id=setting_id, workflow_name=workflow_name
326
+ )
327
+ if not result:
328
+ return None
329
+
330
+ workflow, alias_id = result
331
+ install_request = VendorWorkflowInstallRequest(
332
+ id=workflow.id,
333
+ flowAliasId=alias_id,
334
+ setting_id=setting_id,
335
+ )
336
+
337
+ install_response = self.install_workflows(
338
+ vendor=vendor, workflows=[install_request]
339
+ )
340
+
341
+ if install_response.summary:
342
+ return install_response.summary[0].aiRunId
343
+
344
+ return None
345
+
346
+ def install_first_available_workflow(
347
+ self,
348
+ vendor: VendorType,
349
+ setting_id: str,
350
+ ) -> Optional[str]:
351
+ """Find and install the first available workflow.
352
+
353
+ Args:
354
+ vendor: Cloud vendor type
355
+ setting_id: ID of the vendor setting
356
+
357
+ Returns:
358
+ CodeMie ID of the installed workflow or None if no workflow available
359
+ """
360
+ result = self.find_first_available_workflow(
361
+ vendor=vendor, setting_id=setting_id
362
+ )
363
+ if not result:
364
+ return None
365
+
366
+ workflow, alias_id = result
367
+ install_request = VendorWorkflowInstallRequest(
368
+ id=workflow.id,
369
+ flowAliasId=alias_id,
370
+ setting_id=setting_id,
371
+ )
372
+
373
+ install_response = self.install_workflows(
374
+ vendor=vendor, workflows=[install_request]
375
+ )
376
+
377
+ if install_response.summary:
378
+ return install_response.summary[0].aiRunId
379
+
380
+ return None
381
+
382
+ def find_setting_for_integration(
383
+ self,
384
+ vendor: VendorType,
385
+ integration_id: str,
386
+ ):
387
+ """Find setting for an integration by paginating through all settings.
388
+
389
+ Args:
390
+ vendor: Type of vendor (AWS, AZURE, GCP)
391
+ integration_id: ID of the integration to find (searches by setting_id)
392
+
393
+ Returns:
394
+ VendorWorkflowSetting or None if not found
395
+ """
396
+ page = 0
397
+ per_page = 50
398
+
399
+ while True:
400
+ settings_response = self.get_workflow_settings(
401
+ vendor=vendor,
402
+ page=page,
403
+ per_page=per_page,
404
+ )
405
+
406
+ # Find the setting for our integration by setting_id
407
+ for s in settings_response.data:
408
+ if s.setting_id == integration_id:
409
+ return s
410
+
411
+ # Check if there are more pages
412
+ if page >= settings_response.pagination.pages - 1:
413
+ break
414
+
415
+ page += 1
416
+
417
+ logger.warning(f"Setting not found for integration ID '{integration_id}'")
418
+ return None
@@ -0,0 +1 @@
1
+ """Tests for 3rd party vendor assistants (AWS Bedrock, Azure, GCP)."""
@@ -0,0 +1,65 @@
1
+ import pytest
2
+ from hamcrest import assert_that, is_not, none, empty, is_
3
+ from codemie_test_harness.tests.test_data.vendor_test_data import (
4
+ vendor_assistant_test_data,
5
+ )
6
+
7
+
8
+ @pytest.mark.vendor
9
+ @pytest.mark.api
10
+ @pytest.mark.parametrize(
11
+ "vendor_type,credential_type,credentials,prompt,expected_response",
12
+ vendor_assistant_test_data,
13
+ )
14
+ def test_vendor_assistant_installation_and_chat(
15
+ vendor_assistant_utils,
16
+ integration,
17
+ assistant_utils,
18
+ similarity_check,
19
+ vendor_type,
20
+ credential_type,
21
+ credentials,
22
+ prompt,
23
+ expected_response,
24
+ ):
25
+ """Test vendor assistant installation and chat functionality."""
26
+ _integration = integration(credential_type, credentials)
27
+
28
+ setting = vendor_assistant_utils.find_setting_for_integration(
29
+ vendor=vendor_type,
30
+ integration_id=_integration.id,
31
+ )
32
+ assert_that(setting, is_not(none()))
33
+ assert_that(setting.invalid or False, is_(False))
34
+
35
+ assistants_response = vendor_assistant_utils.get_assistants(
36
+ vendor=vendor_type,
37
+ setting_id=setting.setting_id,
38
+ )
39
+ assert_that(assistants_response, is_not(none()))
40
+ assert_that(assistants_response.data, is_not(empty()))
41
+
42
+ codemie_id = vendor_assistant_utils.install_first_available_assistant(
43
+ vendor=vendor_type,
44
+ setting_id=setting.setting_id,
45
+ )
46
+ assert_that(codemie_id, is_not(none()))
47
+
48
+ class AssistantIdWrapper:
49
+ def __init__(self, assistant_id):
50
+ self.id = assistant_id
51
+
52
+ assistant_wrapper = AssistantIdWrapper(codemie_id)
53
+ response = assistant_utils.ask_assistant(
54
+ assistant=assistant_wrapper,
55
+ user_prompt=prompt,
56
+ minimal_response=True,
57
+ )
58
+
59
+ similarity_check.check_similarity(response, expected_response)
60
+
61
+ uninstall_response = vendor_assistant_utils.uninstall_assistant(
62
+ vendor=vendor_type,
63
+ codemie_id=codemie_id,
64
+ )
65
+ assert_that(uninstall_response.success, is_(True))