11import sys
22import time
3+ from pathlib import Path
4+ from typing import Optional
35from uuid import UUID
46
57import click
68import httpx
79
10+ from junit_xml import TestSuite , TestCase
11+
812from onekey_client import Client
913from onekey_client .queries import load_query
1014
@@ -165,6 +169,94 @@ def get_firmware_compare_ui_url(self, recent_id, firmware_id):
165169 return f"https://{ self .client .api_url .host } /firmwares/compare-firmwares?baseFirmwareId={ recent_id } &otherFirmwareId={ firmware_id } "
166170
167171
172+ class JUnitExporter :
173+ def __init__ (self , client : Client , firmware_id : UUID ):
174+ self .client = client
175+ self .firmware_id = str (firmware_id )
176+
177+ def create_new_issue_testcase (self , issue ):
178+ url = self .get_firmware_issues_ui_url ()
179+ test_case = TestCase (
180+ name = issue ["id" ],
181+ classname = f"Issue: { issue ['type' ]} " ,
182+ file = issue ["file" ]["path" ],
183+ status = "NEW" ,
184+ url = url ,
185+ )
186+
187+ test_case .add_failure_info (
188+ message = "New issue" ,
189+ output = f"""New issue detected
190+ URL: { url }
191+ Type: { issue ["type" ]}
192+ Severity: { issue ["severity" ]}
193+ File: { issue ["file" ]["path" ]}
194+ """ ,
195+ )
196+ return test_case
197+
198+ def create_new_cve_testcase (self , cve ):
199+ cve = dict (cve )
200+ url = self .get_firmware_cves_ui_url ()
201+ test_case = TestCase (name = cve ["id" ], classname = "CVE" , status = "NEW" , url = url )
202+ test_case .add_failure_info (
203+ message = "New CVE" ,
204+ output = f"""New CVE detected
205+ URL: { url }
206+ CVE ID: { cve ['id' ]}
207+ Severity: { cve ['severity' ]}
208+ Description: { cve ['description' ]}
209+ """ ,
210+ )
211+ return test_case
212+
213+ def generate_junit_xml (
214+ self , new_issues , dropped_issues , new_cves , dropped_cves , output_path : Path
215+ ):
216+ new_issues_test_cases = [
217+ self .create_new_issue_testcase (issue ) for issue in new_issues
218+ ]
219+
220+ dropped_issues_test_cases = [
221+ TestCase (
222+ name = issue ["id" ],
223+ classname = f"Issue: { issue ['type' ]} " ,
224+ file = issue ["file" ]["path" ],
225+ status = "DROPPED" ,
226+ url = self .get_firmware_issues_ui_url (),
227+ )
228+ for issue in dropped_issues
229+ ]
230+
231+ new_cves_test_cases = [self .create_new_cve_testcase (cve ) for cve in new_cves ]
232+ dropped_cves_test_cases = [
233+ TestCase (
234+ name = cve_id ,
235+ classname = "CVE" ,
236+ status = "DROPPED" ,
237+ url = self .get_firmware_cves_ui_url (),
238+ )
239+ for cve_id in dropped_cves
240+ ]
241+
242+ issues_test_suite = TestSuite (
243+ "ONEKEY identified issues" ,
244+ new_issues_test_cases + dropped_issues_test_cases ,
245+ )
246+ cves_test_suite = TestSuite (
247+ "ONEKEY identified CVE entries" ,
248+ new_cves_test_cases + dropped_cves_test_cases ,
249+ )
250+ with output_path .open ("w" ) as f :
251+ TestSuite .to_file (f , [issues_test_suite , cves_test_suite ])
252+
253+ def get_firmware_issues_ui_url (self ):
254+ return f"https://{ self .client .api_url .host } /firmwares/issues?firmwareId={ self .firmware_id } "
255+
256+ def get_firmware_cves_ui_url (self ):
257+ return f"https://{ self .client .api_url .host } /firmwares/cves?firmwareId={ self .firmware_id } "
258+
259+
168260@click .command ()
169261@click .option ("--firmware-id" , required = True , type = UUID , help = "Firmware ID" )
170262@click .option (
@@ -196,6 +288,11 @@ def get_firmware_compare_ui_url(self, recent_id, firmware_id):
196288 show_default = True ,
197289 help = "Wait time between retries due to communication problem" ,
198290)
291+ @click .option (
292+ "--junit-path" ,
293+ type = click .Path (exists = False , path_type = Path ),
294+ help = "File to export JUNIT xml" ,
295+ )
199296@click .pass_obj
200297def ci_result (
201298 client : Client ,
@@ -204,6 +301,7 @@ def ci_result(
204301 retry_count : int ,
205302 retry_wait : int ,
206303 check_interval : int ,
304+ junit_path : Optional [Path ],
207305):
208306 """Fetch analysis results for CI"""
209307
@@ -216,6 +314,12 @@ def ci_result(
216314 )
217315 new_issues , dropped_issues , new_cves , dropped_cves = handler .get_result ()
218316
317+ if junit_path is not None :
318+ junit_exporter = JUnitExporter (client , firmware_id )
319+ junit_exporter .generate_junit_xml (
320+ new_issues , dropped_issues , new_cves , dropped_cves , junit_path
321+ )
322+
219323 exit_code = exit_code if new_issues or new_cves else 0
220324
221325 sys .exit (exit_code )
0 commit comments