testit-python-commons 5.1.4__py3-none-any.whl → 5.1.5__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.
@@ -30,7 +30,6 @@ from adapters_api.models import (
30
30
  from testit_python_commons.client.client_configuration import ClientConfiguration
31
31
  from testit_python_commons.client.converter import Converter
32
32
  from testit_python_commons.client.helpers.bulk_autotest_helper import BulkAutotestHelper
33
- from testit_python_commons.client.helpers.test_result_matching import pick_best_in_progress_id
34
33
  from typing import List
35
34
 
36
35
  from testit_python_commons.models.link import Link
@@ -63,7 +62,6 @@ class ApiClientWorker:
63
62
  self.__workflows_api = WorkflowsApi(api_client=api_client)
64
63
  self.__config = config
65
64
  self.__status_codes = self.__get_status_codes()
66
- self.__claimed_in_progress_ids = set()
67
65
 
68
66
  @staticmethod
69
67
  @adapter_logger
@@ -263,15 +261,34 @@ class ApiClientWorker:
263
261
 
264
262
  @adapter_logger
265
263
  @retry
266
- def write_tests(self, test_results: List[TestResult], fixture_containers: dict) -> None:
264
+ def write_tests(
265
+ self,
266
+ test_results: List[TestResult],
267
+ fixture_containers: dict,
268
+ finalized_external_ids: set = None) -> None:
267
269
  logging.debug("call __write_tests")
270
+ finalized_external_ids = finalized_external_ids or set()
268
271
  bulk_autotest_helper = BulkAutotestHelper(self.__autotest_api, self.__test_run_api, self.__config)
269
272
  create_count = 0
270
273
  update_count = 0
274
+ skip_count = 0
271
275
  tests_to_link_after_create = []
272
276
 
273
277
  for test_result in test_results:
274
278
  test_result = self.__add_fixtures_to_test_result(test_result, fixture_containers)
279
+ external_id = test_result.get_external_id()
280
+
281
+ if external_id in finalized_external_ids:
282
+ skip_count += 1
283
+ logging.info(
284
+ 'Bulk import: skip sendTestResults for %s (already finalized at test finish)',
285
+ external_id,
286
+ )
287
+ autotests = self.__get_autotests_by_external_id(external_id)
288
+ if autotests:
289
+ self.__update_auto_test(test_result, autotests[0])
290
+ continue
291
+
275
292
  should_create_work_item = test_result.get_automatic_creation_test_cases()
276
293
 
277
294
  test_result_model = Converter.test_result_to_testrun_result_post_model(
@@ -317,9 +334,10 @@ class ApiClientWorker:
317
334
  tests_to_link_after_create.append(test_result)
318
335
 
319
336
  logging.info(
320
- "Bulk write summary: create=%d, update=%d, total=%d",
337
+ "Bulk write summary: create=%d, update=%d, skip=%d, total=%d",
321
338
  create_count,
322
339
  update_count,
340
+ skip_count,
323
341
  len(test_results),
324
342
  )
325
343
  bulk_autotest_helper.teardown()
@@ -536,87 +554,9 @@ class ApiClientWorker:
536
554
 
537
555
  logging.debug(f'Autotest was linked with workItem "{work_item_id}" by global id "{autotest_global_id}')
538
556
 
539
- @adapter_logger
540
- def find_in_progress_test_result_id(self, external_id: str, parameters=None):
541
- if not external_id:
542
- return None
543
-
544
- candidates = []
545
- for item in self.__get_test_results():
546
- if item is None:
547
- continue
548
- if getattr(item, "autotest_external_id", None) != external_id:
549
- continue
550
- result_id = getattr(item, "id", None)
551
- if result_id is None:
552
- continue
553
- result_id_str = str(result_id)
554
- if result_id_str in self.__claimed_in_progress_ids:
555
- continue
556
- try:
557
- detail = self.get_test_result_by_id(result_id_str)
558
- test_point_id = getattr(detail, "test_point_id", None)
559
- parameters_meta = getattr(detail, "parameters", None) or {}
560
- except Exception as exc:
561
- logging.debug("getTestResult %s failed: %s", result_id_str, exc)
562
- test_point_id = None
563
- parameters_meta = {}
564
- candidates.append({
565
- "id": result_id_str,
566
- "has_test_point": self.__is_valid_test_point_id(test_point_id),
567
- "parameters": parameters_meta,
568
- })
569
-
570
- chosen = pick_best_in_progress_id(candidates, parameters)
571
- if chosen:
572
- self.__claimed_in_progress_ids.add(chosen)
573
- return chosen
574
-
575
- @staticmethod
576
- def __is_valid_test_point_id(test_point_id) -> bool:
577
- if test_point_id is None:
578
- return False
579
- value = str(test_point_id)
580
- return bool(value) and value != "00000000-0000-0000-0000-000000000000"
581
-
582
- @adapter_logger
583
- @retry
584
- def __update_existing_test_result(self, test_result_id: str, test_result: TestResult) -> str:
585
- existing = self.get_test_result_by_id(test_result_id)
586
- model = Converter.convert_test_result_model_to_test_results_id_put_request(existing)
587
-
588
- outcome = test_result.get_outcome()
589
- if outcome and outcome.upper() in self.__status_codes:
590
- model.status_code = outcome
591
-
592
- if test_result.get_duration() is not None:
593
- model.duration_in_ms = round(test_result.get_duration())
594
- if test_result.get_message() is not None:
595
- model.message = test_result.get_message()
596
- if test_result.get_traces() is not None:
597
- model.trace = test_result.get_traces()
598
-
599
- self.__test_results_api.adapters_test_results_id_put(
600
- id=test_result_id,
601
- adapters_test_results_id_put_request=model,
602
- )
603
- logging.debug(
604
- 'Updated existing test result "%s" for autotest "%s"',
605
- test_result_id,
606
- test_result.get_autotest_name(),
607
- )
608
- return test_result_id
609
-
610
557
  @adapter_logger
611
558
  @retry
612
559
  def __load_test_result(self, test_result: TestResult) -> str:
613
- existing_id = self.find_in_progress_test_result_id(
614
- test_result.get_external_id(),
615
- test_result.get_parameters(),
616
- )
617
- if existing_id:
618
- return self.__update_existing_test_result(existing_id, test_result)
619
-
620
560
  model = Converter.test_result_to_testrun_result_post_model(
621
561
  test_result,
622
562
  self.__config.get_configuration_id(),
@@ -626,10 +566,19 @@ class ApiClientWorker:
626
566
  id=self.__config.get_test_run_id(),
627
567
  auto_test_results_for_test_run_model=[model])
628
568
 
629
- logging.debug(f'Result of the autotest "{test_result.get_autotest_name()}" was set '
630
- f'in the test run "{self.__config.get_test_run_id()}"')
569
+ test_result_id = Converter.get_test_result_id_from_testrun_result_post_response(response)
570
+ logging.debug(
571
+ 'Finalized test result via sendTestResults for %s (resultId=%s)',
572
+ test_result.get_external_id(),
573
+ test_result_id,
574
+ )
575
+ logging.debug(
576
+ 'Result of the autotest "%s" was set in the test run "%s"',
577
+ test_result.get_autotest_name(),
578
+ self.__config.get_test_run_id(),
579
+ )
631
580
 
632
- return Converter.get_test_result_id_from_testrun_result_post_response(response)
581
+ return test_result_id
633
582
 
634
583
  @adapter_logger
635
584
  @retry
@@ -638,6 +587,8 @@ class ApiClientWorker:
638
587
 
639
588
  @adapter_logger
640
589
  def update_test_results(self, fixtures_containers: dict, test_result_ids: dict) -> None:
590
+ # PUT only setup/teardown fixtures — never status or step_results.
591
+ # See docs/test-result-export-contract.md
641
592
  test_results = Converter.fixtures_containers_to_test_results_with_all_fixture_step_results(
642
593
  fixtures_containers, test_result_ids)
643
594
 
@@ -189,10 +189,9 @@ class AdapterManager:
189
189
  self.__sync_storage_runner.set_is_already_in_progress(True)
190
190
 
191
191
  try:
192
- # Keep final outcome. Prefer PUT on existing TP-bound InProgress (mode=0);
193
- # never invent a second InProgress via setAutoTestResultsForTestRun.
192
+ # Final status via sendTestResults (create); never change status via PUT.
194
193
  logging.debug(
195
- "SyncStorage accepted %s; write final status (update existing if present)",
194
+ "SyncStorage accepted %s; sendTestResults with final status",
196
195
  test_result.get_external_id(),
197
196
  )
198
197
  self._write_test_realtime_internal(test_result)
@@ -263,7 +262,11 @@ class AdapterManager:
263
262
  for test_result in self.__test_results:
264
263
  test_result.set_automatic_creation_test_cases(should_create_work_item)
265
264
 
266
- self.__api_client.write_tests(self.__test_results, fixtures)
265
+ self.__api_client.write_tests(
266
+ self.__test_results,
267
+ fixtures,
268
+ finalized_external_ids=set(self.__test_result_map.keys()),
269
+ )
267
270
 
268
271
  @adapter_logger
269
272
  def load_attachments(self, attach_paths):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testit-python-commons
3
- Version: 5.1.4
3
+ Version: 5.1.5
4
4
  Summary: Python commons for Test IT
5
5
  Home-page: https://github.com/testit-tms/adapters-python/
6
6
  Author: Integration team
@@ -33,7 +33,9 @@ Dynamic: summary
33
33
 
34
34
  This library includes built-in support for Sync Storage, enabling coordinated test execution across multiple workers in distributed testing environments.
35
35
 
36
- See [SYNC_STORAGE_INTEGRATION.md](SYNC_STORAGE_INTEGRATION.md) for detailed documentation on how the integration works.
36
+ See [sync-storage-interaction-spec.md](sync-storage-interaction-spec.md) for the protocol.
37
+
38
+ **Test result export (POST vs PUT):** [../docs/test-result-export-contract.md](../docs/test-result-export-contract.md)
37
39
 
38
40
  # How to enable debug logging?
39
41
  1. Add in **connection_config.ini** file from the root directory of the project:
@@ -227,7 +227,7 @@ testit_python_commons/decorators.py,sha256=QI8qMjDincTRZmcZByP5y5wHsf_Fwd1oc67Wu
227
227
  testit_python_commons/dynamic_methods.py,sha256=WKHHA6Uj61tWXI3DphALYOi2BV6TnQKvQy0-Vt2JR2I,8816
228
228
  testit_python_commons/step.py,sha256=CYUs_S8opmRg1tsTY1dVJOPzzt-HYR3_uLTBpYtHSfQ,3244
229
229
  testit_python_commons/client/__init__.py,sha256=zSJhxDHzWWTFHJ0s8bqCaBtM5FK2GcPgZxaKdI8zf-o,165
230
- testit_python_commons/client/api_client.py,sha256=FX3wnTZGNsMbTA0_79k_4kkm10ezYsdtvJ1Q7BoAcEI,27359
230
+ testit_python_commons/client/api_client.py,sha256=SgIPi9AG8o5jAATggZeiUvK7Hffuir_Z7727AJT3OEs,25274
231
231
  testit_python_commons/client/client_configuration.py,sha256=1M-OrROU3Xj0YGsUX4venG-l6Ewr43LNvUcyFfY57ys,2776
232
232
  testit_python_commons/client/converter.py,sha256=eZ0GR6DaAdrQeTzKpzGYLPEcGbkc7lTwiVwKGn6cPxE,26998
233
233
  testit_python_commons/client/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -250,7 +250,7 @@ testit_python_commons/models/step_result.py,sha256=T9BbcPB6BCz3ZkrWXy2Z2Guj2wecv
250
250
  testit_python_commons/models/test_result.py,sha256=z6OB3V_whCQDJvr4jQdgVanoJSoeKh8ZXgPAACkNrdw,7515
251
251
  testit_python_commons/models/test_result_with_all_fixture_step_results_model.py,sha256=aI5nfJ_4EAhzZg7eIV1JhSf9i3EENiRiuQuqJvSUs3M,866
252
252
  testit_python_commons/services/__init__.py,sha256=yVVmiCgMyike_8OoNPiHLNoMqKhCDlWYhvmGW0IRHJM,1197
253
- testit_python_commons/services/adapter_manager.py,sha256=HiPxT5Ikbpdqe41EO-mmiUXvmDPms6ZRnsTCVdnybuE,11839
253
+ testit_python_commons/services/adapter_manager.py,sha256=UJK0c8aj4X2UI8rFNR2wYtPnHWDOA8c5K0dAQeDOIRU,11849
254
254
  testit_python_commons/services/adapter_manager_configuration.py,sha256=ZPgSRUskSh8sJNAtlpOSAXqGWWTdYNsDb3jaiuTFEc8,2228
255
255
  testit_python_commons/services/fixture_manager.py,sha256=PCSLBEPa3yJVbCPsMt_Y5badWLAhKrJsZTaUesq7oNI,2006
256
256
  testit_python_commons/services/fixture_storage.py,sha256=nAIW0p9lVvVWtiVdFNqaD7-gCBislY8DLrfdlh8OqFw,1205
@@ -265,7 +265,7 @@ testit_python_commons/services/sync_storage/__init__.py,sha256=Qmjs6Xk5l-i5FFItk
265
265
  testit_python_commons/services/sync_storage/sync_storage_runner.py,sha256=9FCbMxzGa84I79X7QOjYO_oSiUUu43LhJqPDs_JEEb0,20549
266
266
  testit_python_commons/utils/__init__.py,sha256=CUHziylwvFAuKyUM1tCp13ik5Z2Mw-VK8n7c92aVy-I,78
267
267
  testit_python_commons/utils/html_escape_utils.py,sha256=wx20lAVrMvhNiQ7bEu_f-SGqDBp85D-EBL2sgTmnJPY,8135
268
- testit_python_commons-5.1.4.dist-info/METADATA,sha256=k9x4s68LToNIUED1RLgLV2mYUYH6-0JI6N7vDpU1TsM,1675
269
- testit_python_commons-5.1.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
270
- testit_python_commons-5.1.4.dist-info/top_level.txt,sha256=TuyfRneOyFuvNz83bbn-pWaAR1fY7FINrrJRTW9Lyqg,65
271
- testit_python_commons-5.1.4.dist-info/RECORD,,
268
+ testit_python_commons-5.1.5.dist-info/METADATA,sha256=SwurHeovR4YAEBseTc0FHT0NdiWh3X3bwWXCjVIFL74,1766
269
+ testit_python_commons-5.1.5.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
270
+ testit_python_commons-5.1.5.dist-info/top_level.txt,sha256=TuyfRneOyFuvNz83bbn-pWaAR1fY7FINrrJRTW9Lyqg,65
271
+ testit_python_commons-5.1.5.dist-info/RECORD,,