• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

testit-tms / adapters-python / 18941451153

30 Oct 2025 01:00PM UTC coverage: 40.907% (+4.0%) from 36.89%
18941451153

push

github

web-flow
feat: TMS-35016: support for updating the test run name for all adapt… (#211)

* feat: TMS-35016: support for updating the test run name for all adapter modes.

* code review changes

---------

Co-authored-by: pavel.butuzov <pavel.butuzov@testit.software>

26 of 54 new or added lines in 4 files covered. (48.15%)

1028 of 2513 relevant lines covered (40.91%)

1.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.72
/testit-python-commons/src/testit_python_commons/client/converter.py
1
import logging
2✔
2
from typing import List
2✔
3

4
from testit_api_client.models import (
2✔
5
    ApiV2TestResultsSearchPostRequest,
6
    AutoTestApiResult,
7
    AttachmentPutModelAutoTestStepResultsModel,
8
    AutoTestStepResultUpdateRequest,
9
    CreateAutoTestRequest,
10
    UpdateAutoTestRequest,
11
    AutoTestPostModel,
12
    AutoTestPutModel,
13
    AutoTestResultsForTestRunModel,
14
    AutoTestStepModel,
15
    AvailableTestResultOutcome,
16
    LinkPostModel,
17
    LinkPutModel,
18
    LinkType,
19
    CreateEmptyRequest,
20
    TestRunV2ApiResult,
21
    TestResultShortResponse,
22
    AutoTestSearchApiModelFilter,
23
    AutoTestSearchApiModelIncludes,
24
    ApiV2AutoTestsSearchPostRequest,
25
    ApiV2TestResultsIdPutRequest,
26
    TestResultResponse,
27
    AttachmentApiResult,
28
    AttachmentUpdateRequest,
29
    UpdateEmptyRequest,
30
    LinkApiResult,
31
    UpdateLinkApiModel,
32
    AssignAttachmentApiModel,
33
)
34

35
from testit_python_commons.models.link import Link
2✔
36
from testit_python_commons.models.step_result import StepResult
2✔
37
from testit_python_commons.models.test_result import TestResult
2✔
38
from testit_python_commons.models.test_result_with_all_fixture_step_results_model import TestResultWithAllFixtureStepResults
2✔
39
from testit_python_commons.services.logger import adapter_logger
2✔
40

41

42
class Converter:
2✔
43
    @staticmethod
2✔
44
    @adapter_logger
2✔
45
    def test_run_to_test_run_short_model(project_id: str, name: str) -> CreateEmptyRequest:
2✔
46
        return CreateEmptyRequest(
×
47
            project_id=project_id,
48
            name=name
49
        )
50

51
    @staticmethod
2✔
52
    @adapter_logger
2✔
53
    def get_id_from_create_test_run_response(response: TestRunV2ApiResult) -> str:
2✔
54
        return response.id
×
55

56
    @classmethod
2✔
57
    @adapter_logger
2✔
58
    def build_update_empty_request(cls, test_run: TestRunV2ApiResult) -> UpdateEmptyRequest:
2✔
NEW
59
        return UpdateEmptyRequest(
×
60
            id=test_run.id,
61
            name=test_run.name,
62
            description=test_run.description,
63
            launch_source=test_run.launch_source,
64
            attachments=list(map(cls.build_assign_attachment_api_model, test_run.attachments)),
65
            links=list(map(cls.build_update_link_api_model, test_run.links))
66
        )
67

68
    @staticmethod
2✔
69
    @adapter_logger
2✔
70
    def build_update_link_api_model(link: LinkApiResult) -> UpdateLinkApiModel:
2✔
NEW
71
        return UpdateLinkApiModel(
×
72
            id=link.id,
73
            title=link.title,
74
            description=link.description,
75
            type=link.type,
76
            url=link.url,
77
            has_info=link.has_info
78
        )
79

80
    @staticmethod
2✔
81
    @adapter_logger
2✔
82
    def build_assign_attachment_api_model(attachment: AttachmentApiResult) -> AssignAttachmentApiModel:
2✔
NEW
83
        return AssignAttachmentApiModel(id=attachment.id)
×
84

85
    @staticmethod
2✔
86
    @adapter_logger
2✔
87
    def project_id_and_external_id_to_auto_tests_search_post_request(
2✔
88
            project_id: str,
89
            external_id: str
90
    ) -> ApiV2AutoTestsSearchPostRequest:
91
        autotests_filter = AutoTestSearchApiModelFilter(
×
92
            project_ids=[project_id],
93
            external_ids=[external_id],
94
            is_deleted=False)
95
        autotests_includes = AutoTestSearchApiModelIncludes(
×
96
            include_steps=False,
97
            include_links=False,
98
            include_labels=False)
99

100
        return ApiV2AutoTestsSearchPostRequest(filter=autotests_filter, includes=autotests_includes)
×
101

102
    @staticmethod
2✔
103
    @adapter_logger
2✔
104
    def build_test_results_search_post_request_with_in_progress_outcome(
2✔
105
            testrun_id: str,
106
            configuration_id: str) -> ApiV2TestResultsSearchPostRequest:
107
        return ApiV2TestResultsSearchPostRequest(
×
108
            test_run_ids=[testrun_id],
109
            configuration_ids=[configuration_id],
110
            status_codes=["InProgress"])
111

112
    @staticmethod
2✔
113
    @adapter_logger
2✔
114
    def autotest_ids_to_autotests_search_post_request(
2✔
115
            autotest_ids: List[int]) -> ApiV2AutoTestsSearchPostRequest:
116
        autotests_filter = AutoTestSearchApiModelFilter(
×
117
            global_ids=autotest_ids)
118
        autotests_includes = AutoTestSearchApiModelIncludes(
×
119
            include_steps=False,
120
            include_links=False,
121
            include_labels=False)
122

123
        return ApiV2AutoTestsSearchPostRequest(filter=autotests_filter, includes=autotests_includes)
×
124

125
    @staticmethod
2✔
126
    @adapter_logger
2✔
127
    def get_external_ids_from_autotest_response_list(
2✔
128
            autotests: List[TestResultShortResponse],
129
            configuration: str) -> List[str]:
130
        external_ids: List[str] = []
×
131

132
        for autotest in autotests:
×
133
            if configuration == autotest.configuration_id:
×
134
                external_ids.append(autotest.autotest_external_id)
×
135

136
        return external_ids
×
137

138
    @classmethod
2✔
139
    @adapter_logger
2✔
140
    def test_result_to_autotest_post_model(
2✔
141
            cls,
142
            test_result: TestResult,
143
            project_id: str) -> AutoTestPostModel:
144
        return AutoTestPostModel(
×
145
            external_id=test_result.get_external_id(),
146
            project_id=project_id,
147
            name=test_result.get_autotest_name(),
148
            steps=cls.step_results_to_autotest_steps_model(
149
                test_result.get_step_results()),
150
            setup=cls.step_results_to_autotest_steps_model(
151
                test_result.get_setup_results()),
152
            teardown=cls.step_results_to_autotest_steps_model(
153
                test_result.get_teardown_results()),
154
            namespace=test_result.get_namespace(),
155
            classname=test_result.get_classname(),
156
            title=test_result.get_title(),
157
            description=test_result.get_description(),
158
            links=cls.links_to_links_post_model(test_result.get_links()),
159
            labels=test_result.get_labels(),
160
            should_create_work_item=test_result.get_automatic_creation_test_cases(),
161
            external_key=test_result.get_external_key()
162
        )
163

164
    @classmethod
2✔
165
    @adapter_logger
2✔
166
    def test_result_to_create_autotest_request(
2✔
167
            cls,
168
            test_result: TestResult,
169
            project_id: str) -> CreateAutoTestRequest:
170
        return CreateAutoTestRequest(
×
171
            external_id=test_result.get_external_id(),
172
            project_id=project_id,
173
            name=test_result.get_autotest_name(),
174
            steps=cls.step_results_to_autotest_steps_model(
175
                test_result.get_step_results()),
176
            setup=cls.step_results_to_autotest_steps_model(
177
                test_result.get_setup_results()),
178
            teardown=cls.step_results_to_autotest_steps_model(
179
                test_result.get_teardown_results()),
180
            namespace=test_result.get_namespace(),
181
            classname=test_result.get_classname(),
182
            title=test_result.get_title(),
183
            description=test_result.get_description(),
184
            links=cls.links_to_links_post_model(test_result.get_links()),
185
            labels=test_result.get_labels(),
186
            should_create_work_item=test_result.get_automatic_creation_test_cases(),
187
            external_key=test_result.get_external_key()
188
        )
189

190
    @classmethod
2✔
191
    @adapter_logger
2✔
192
    def test_result_to_autotest_put_model(
2✔
193
            cls,
194
            test_result: TestResult,
195
            project_id: str) -> AutoTestPutModel:
196
        if test_result.get_outcome() == 'Passed':
×
197
            return AutoTestPutModel(
×
198
                external_id=test_result.get_external_id(),
199
                project_id=project_id,
200
                name=test_result.get_autotest_name(),
201
                steps=cls.step_results_to_autotest_steps_model(
202
                    test_result.get_step_results()),
203
                setup=cls.step_results_to_autotest_steps_model(
204
                    test_result.get_setup_results()),
205
                teardown=cls.step_results_to_autotest_steps_model(
206
                    test_result.get_teardown_results()),
207
                namespace=test_result.get_namespace(),
208
                classname=test_result.get_classname(),
209
                title=test_result.get_title(),
210
                description=test_result.get_description(),
211
                links=cls.links_to_links_put_model(test_result.get_links()),
212
                labels=test_result.get_labels(),
213
                external_key=test_result.get_external_key()
214
            )
215
        else:
216
            return AutoTestPutModel(
×
217
                external_id=test_result.get_external_id(),
218
                project_id=project_id,
219
                name=test_result.get_autotest_name(),
220
                steps=cls.step_results_to_autotest_steps_model(
221
                    test_result.get_step_results()),
222
                setup=cls.step_results_to_autotest_steps_model(
223
                    test_result.get_setup_results()),
224
                teardown=cls.step_results_to_autotest_steps_model(
225
                    test_result.get_teardown_results()),
226
                namespace=test_result.get_namespace(),
227
                classname=test_result.get_classname(),
228
                title=test_result.get_title(),
229
                description=test_result.get_description(),
230
                links=cls.links_to_links_put_model(test_result.get_links()),
231
                labels=test_result.get_labels(),
232
                external_key=test_result.get_external_key()
233
            )
234

235
    @classmethod
2✔
236
    @adapter_logger
2✔
237
    def test_result_to_update_autotest_request(
2✔
238
            cls,
239
            test_result: TestResult,
240
            project_id: str) -> UpdateAutoTestRequest:
241
        if test_result.get_outcome() == 'Passed':
×
242
            return UpdateAutoTestRequest(
×
243
                external_id=test_result.get_external_id(),
244
                project_id=project_id,
245
                name=test_result.get_autotest_name(),
246
                steps=cls.step_results_to_autotest_steps_model(
247
                    test_result.get_step_results()),
248
                setup=cls.step_results_to_autotest_steps_model(
249
                    test_result.get_setup_results()),
250
                teardown=cls.step_results_to_autotest_steps_model(
251
                    test_result.get_teardown_results()),
252
                namespace=test_result.get_namespace(),
253
                classname=test_result.get_classname(),
254
                title=test_result.get_title(),
255
                description=test_result.get_description(),
256
                links=cls.links_to_links_put_model(test_result.get_links()),
257
                labels=test_result.get_labels(),
258
                external_key=test_result.get_external_key()
259
            )
260
        else:
261
            return UpdateAutoTestRequest(
×
262
                external_id=test_result.get_external_id(),
263
                project_id=project_id,
264
                name=test_result.get_autotest_name(),
265
                steps=cls.step_results_to_autotest_steps_model(
266
                    test_result.get_step_results()),
267
                setup=cls.step_results_to_autotest_steps_model(
268
                    test_result.get_setup_results()),
269
                teardown=cls.step_results_to_autotest_steps_model(
270
                    test_result.get_teardown_results()),
271
                namespace=test_result.get_namespace(),
272
                classname=test_result.get_classname(),
273
                title=test_result.get_title(),
274
                description=test_result.get_description(),
275
                links=cls.links_to_links_put_model(test_result.get_links()),
276
                labels=test_result.get_labels(),
277
                external_key=test_result.get_external_key()
278
            )
279

280
    @classmethod
2✔
281
    @adapter_logger
2✔
282
    def test_result_to_testrun_result_post_model(
2✔
283
            cls,
284
            test_result: TestResult,
285
            configuration_id: str) -> AutoTestResultsForTestRunModel:
286
        return AutoTestResultsForTestRunModel(
×
287
            configuration_id=configuration_id,
288
            auto_test_external_id=test_result.get_external_id(),
289
            status_code=test_result.get_outcome(),
290
            step_results=cls.step_results_to_attachment_put_model_autotest_step_results_model(
291
                test_result.get_step_results()),
292
            setup_results=cls.step_results_to_attachment_put_model_autotest_step_results_model(
293
                test_result.get_setup_results()),
294
            teardown_results=cls.step_results_to_attachment_put_model_autotest_step_results_model(
295
                test_result.get_teardown_results()),
296
            traces=test_result.get_traces(),
297
            attachments=test_result.get_attachments(),
298
            parameters=test_result.get_parameters(),
299
            properties=test_result.get_properties(),
300
            links=cls.links_to_links_post_model(
301
                test_result.get_result_links()),
302
            duration=round(test_result.get_duration()),
303
            message=test_result.get_message(),
304
            started_on=test_result.get_started_on(),
305
            completed_on=test_result.get_completed_on()
306
        )
307

308
    @classmethod
2✔
309
    @adapter_logger
2✔
310
    def convert_test_result_model_to_test_results_id_put_request(
2✔
311
            cls,
312
            test_result: TestResultResponse) -> ApiV2TestResultsIdPutRequest:
313
        return ApiV2TestResultsIdPutRequest(
×
314
            failure_class_ids=test_result.failure_class_ids,
315
            status_code=test_result.status.code,
316
            comment=test_result.comment,
317
            links=test_result.links,
318
            step_results=test_result.step_results,
319
            attachments=cls.attachment_models_to_attachment_put_models(test_result.attachments),
320
            duration_in_ms=test_result.duration_in_ms,
321
            step_comments=test_result.step_comments,
322
            # setup_results=test_result.setup_results,
323
            # teardown_results=test_result.teardown_results,
324
            message=test_result.message,
325
            trace=test_result.traces)
326

327
    @classmethod
2✔
328
    @adapter_logger
2✔
329
    def convert_test_result_with_all_setup_and_teardown_steps_to_test_results_id_put_request(
2✔
330
            cls,
331
            test_result: TestResultWithAllFixtureStepResults) -> ApiV2TestResultsIdPutRequest:
332
        return ApiV2TestResultsIdPutRequest(
×
333
            setup_results=cls.step_results_to_attachment_put_model_autotest_step_results_model(
334
                test_result.get_setup_results()),
335
            teardown_results=cls.step_results_to_attachment_put_model_autotest_step_results_model(
336
                test_result.get_teardown_results()))
337

338
    @classmethod
2✔
339
    @adapter_logger
2✔
340
    def get_test_result_id_from_testrun_result_post_response(cls, response) -> str:
2✔
341
        return response[0]
×
342

343
    @staticmethod
2✔
344
    @adapter_logger
2✔
345
    def link_to_link_post_model(link: Link) -> LinkPostModel:
2✔
346
        if link.get_link_type():
×
347
            return LinkPostModel(
×
348
                url=link.get_url(),
349
                title=link.get_title(),
350
                type=LinkType(link.get_link_type()),
351
                description=link.get_description(),
352
                has_info=True,
353
            )
354
        else:
355
            return LinkPostModel(
×
356
                url=link.get_url(),
357
                title=link.get_title(),
358
                description=link.get_description(),
359
                has_info=True,
360
            )
361

362
    @staticmethod
2✔
363
    @adapter_logger
2✔
364
    def link_to_link_put_model(link: Link) -> LinkPutModel:
2✔
365
        if link.get_link_type():
×
366
            return LinkPutModel(
×
367
                url=link.get_url(),
368
                title=link.get_title(),
369
                type=LinkType(link.get_link_type()),
370
                description=link.get_description(),
371
                has_info=True,
372
            )
373
        else:
374
            return LinkPutModel(
×
375
                url=link.get_url(),
376
                title=link.get_title(),
377
                description=link.get_description(),
378
                has_info=True,
379
            )
380

381
    @classmethod
2✔
382
    @adapter_logger
2✔
383
    def links_to_links_post_model(cls, links: List[Link]) -> List[LinkPostModel]:
2✔
384
        post_model_links = []
×
385

386
        for link in links:
×
387
            post_model_links.append(
×
388
                cls.link_to_link_post_model(link)
389
            )
390

391
        return post_model_links
×
392

393
    @classmethod
2✔
394
    @adapter_logger
2✔
395
    def links_to_links_put_model(cls, links: List[Link]) -> List[LinkPutModel]:
2✔
396
        put_model_links = []
×
397

398
        for link in links:
×
399
            put_model_links.append(
×
400
                cls.link_to_link_put_model(link)
401
            )
402

403
        return put_model_links
×
404

405
    @classmethod
2✔
406
    @adapter_logger
2✔
407
    def attachment_models_to_attachment_put_models(
2✔
408
            cls, attachments: List[AttachmentApiResult]) -> List[AttachmentUpdateRequest]:
409
        put_model_attachments = []
×
410

411
        for attachment in attachments:
×
412
            put_model_attachments.append(
×
413
                cls.attachment_model_to_attachment_put_model(attachment)
414
            )
415

416
        return put_model_attachments
×
417

418
    @staticmethod
2✔
419
    @adapter_logger
2✔
420
    def attachment_model_to_attachment_put_model(attachment: AttachmentApiResult) -> AttachmentUpdateRequest:
2✔
421
        return AttachmentUpdateRequest(id=attachment.id)
×
422

423
    @classmethod
2✔
424
    # @adapter_logger
425
    def step_results_to_autotest_steps_model(
2✔
426
            cls, step_results: List[StepResult]) -> List[AutoTestStepModel]:
427
        autotest_model_steps = []
×
428

429
        for step_result in step_results:
×
430
            autotest_model_steps.append(
×
431
                AutoTestStepModel(
432
                    title=step_result.get_title(),
433
                    description=step_result.get_description(),
434
                    steps=cls.step_results_to_autotest_steps_model(
435
                        step_result.get_step_results()))
436
            )
437

438
        return autotest_model_steps
×
439

440
    @classmethod
2✔
441
    @adapter_logger
2✔
442
    def step_results_to_attachment_put_model_autotest_step_results_model(
2✔
443
            cls, step_results: List[StepResult]) -> List[AttachmentPutModelAutoTestStepResultsModel]:
444
        autotest_model_step_results = []
×
445

446
        for step_result in step_results:
×
447
            autotest_model_step_results.append(
×
448
                AttachmentPutModelAutoTestStepResultsModel(
449
                    title=step_result.get_title(),
450
                    outcome=AvailableTestResultOutcome(step_result.get_outcome()),
451
                    description=step_result.get_description(),
452
                    duration=step_result.get_duration(),
453
                    parameters=step_result.get_parameters(),
454
                    attachments=step_result.get_attachments(),
455
                    started_on=step_result.get_started_on(),
456
                    completed_on=step_result.get_completed_on(),
457
                    step_results=cls.step_results_to_attachment_put_model_autotest_step_results_model(
458
                        step_result.get_step_results())
459
                )
460
            )
461

462
        return autotest_model_step_results
×
463

464
    @staticmethod
2✔
465
    @adapter_logger
2✔
466
    def fixtures_containers_to_test_results_with_all_fixture_step_results(
2✔
467
            fixtures_containers: dict,
468
            test_result_ids: dict) -> List[TestResultWithAllFixtureStepResults]:
469
        test_results_with_all_fixture_step_results = []
×
470

471
        for external_id, test_result_id in test_result_ids.items():
×
472
            test_result_with_all_fixture_step_results = TestResultWithAllFixtureStepResults(test_result_id)
×
473

474
            for uuid, fixtures_container in fixtures_containers.items():
×
475
                if external_id in fixtures_container.external_ids:
×
476
                    if fixtures_container.befores:
×
477
                        test_result_with_all_fixture_step_results.set_setup_results(fixtures_container.befores[0].steps)
×
478

479
                    if fixtures_container.afters:
×
480
                        test_result_with_all_fixture_step_results.set_teardown_results(
×
481
                            fixtures_container.afters[0].steps)
482

483
            test_results_with_all_fixture_step_results.append(test_result_with_all_fixture_step_results)
×
484

485
        return test_results_with_all_fixture_step_results
×
486

487
    @classmethod
2✔
488
    @adapter_logger
2✔
489
    def step_results_to_auto_test_step_result_update_request(
2✔
490
            cls, step_results: List[StepResult]) -> List[AutoTestStepResultUpdateRequest]:
491
        autotest_model_step_results = []
×
492

493
        for step_result in step_results:
×
494
            autotest_model_step_results.append(
×
495
                AutoTestStepResultUpdateRequest(
496
                    title=step_result.get_title(),
497
                    outcome=AvailableTestResultOutcome(step_result.get_outcome()),
498
                    description=step_result.get_description(),
499
                    duration=step_result.get_duration(),
500
                    parameters=step_result.get_parameters(),
501
                    attachments=step_result.get_attachments(),
502
                    started_on=step_result.get_started_on(),
503
                    completed_on=step_result.get_completed_on(),
504
                    step_results=cls.step_results_to_auto_test_step_result_update_request(
505
                        step_result.get_step_results())
506
                )
507
            )
508

509
        return autotest_model_step_results
×
510

511
    @classmethod
2✔
512
    @adapter_logger
2✔
513
    def prepare_to_create_autotest(
2✔
514
            cls,
515
            test_result: TestResult,
516
            project_id: str,
517
            work_item_ids_for_link_with_auto_test: list) -> CreateAutoTestRequest:
518
        logging.debug('Preparing to create the auto test ' + test_result.get_external_id())
×
519

520
        model = cls.test_result_to_create_autotest_request(
×
521
            test_result,
522
            project_id)
523
        model.work_item_ids_for_link_with_auto_test = work_item_ids_for_link_with_auto_test
×
524

525
        return model
×
526

527
    @classmethod
2✔
528
    @adapter_logger
2✔
529
    def prepare_to_mass_create_autotest(
2✔
530
            cls,
531
            test_result: TestResult,
532
            project_id: str,
533
            work_item_ids_for_link_with_auto_test: list) -> AutoTestPostModel:
534
        logging.debug('Preparing to create the auto test ' + test_result.get_external_id())
×
535

536
        model = cls.test_result_to_autotest_post_model(
×
537
            test_result,
538
            project_id)
539
        model.work_item_ids_for_link_with_auto_test = work_item_ids_for_link_with_auto_test
×
540

541
        return model
×
542

543
    @classmethod
2✔
544
    @adapter_logger
2✔
545
    def prepare_to_update_autotest(
2✔
546
            cls,
547
            test_result: TestResult,
548
            autotest: AutoTestApiResult,
549
            project_id: str) -> UpdateAutoTestRequest:
550
        logging.debug('Preparing to update the auto test ' + test_result.get_external_id())
×
551

552
        model = cls.test_result_to_update_autotest_request(
×
553
            test_result,
554
            project_id)
555
        model.is_flaky = autotest.is_flaky
×
556
        # TODO: return after fix PUT/api/v2/autoTests
557
        # model.work_item_ids_for_link_with_auto_test = self.__get_work_item_uuids_for_link_with_auto_test(
558
        #     test_result.get_work_item_ids(),
559
        #     str(autotest.global_id))
560

561
        return model
×
562

563
    @classmethod
2✔
564
    @adapter_logger
2✔
565
    def prepare_to_mass_update_autotest(
2✔
566
            cls,
567
            test_result: TestResult,
568
            autotest: AutoTestApiResult,
569
            project_id: str) -> AutoTestPutModel:
570
        logging.debug('Preparing to update the auto test ' + test_result.get_external_id())
×
571

572
        model = cls.test_result_to_autotest_put_model(
×
573
            test_result,
574
            project_id)
575
        model.is_flaky = autotest.is_flaky
×
576
        # TODO: return after fix PUT/api/v2/autoTests
577
        # model.work_item_ids_for_link_with_auto_test = self.__get_work_item_uuids_for_link_with_auto_test(
578
        #     test_result.get_work_item_ids(),
579
        #     str(autotest.global_id))
580

581
        return model
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc