wiliot-certificate 4.4.3__py3-none-any.whl → 4.5.0a1__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.
Files changed (295) hide show
  1. {brg_certificate → certificate}/ag/wlt_cmd_if.html +10 -4
  2. {brg_certificate → certificate}/ag/wlt_types_ag.py +1878 -519
  3. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/brg2brg_ota.json +69 -0
  4. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/brg2gw_hb.json +109 -0
  5. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +45 -0
  6. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/calibration.json +96 -0
  7. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/custom.json +99 -0
  8. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/datapath.json +241 -13
  9. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/energy2400.json +120 -0
  10. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/energySub1g.json +96 -0
  11. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/externalSensor.json +135 -6
  12. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/interface.json +157 -0
  13. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/powerManagement.json +205 -0
  14. brg_certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json → certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +27 -11
  15. certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json +175 -0
  16. certificate/ag/wlt_types_ag_jsons/unified_sensor_pkt.json +65 -0
  17. certificate/cert_common.py +1459 -0
  18. certificate/cert_config.py +455 -0
  19. {brg_certificate → certificate}/cert_data_sim.py +131 -46
  20. {brg_certificate → certificate}/cert_defines.py +120 -128
  21. {brg_certificate → certificate}/cert_gw_sim.py +151 -49
  22. {brg_certificate → certificate}/cert_mqtt.py +164 -59
  23. {brg_certificate → certificate}/cert_prints.py +34 -33
  24. {brg_certificate → certificate}/cert_protobuf.py +15 -6
  25. {brg_certificate → certificate}/cert_results.py +205 -48
  26. certificate/cert_utils.py +622 -0
  27. certificate/certificate.py +198 -0
  28. certificate/certificate_cli.py +79 -0
  29. certificate/certificate_eth_test_list.txt +74 -0
  30. certificate/certificate_sanity_test_list.txt +65 -0
  31. certificate/certificate_test_list.txt +75 -0
  32. {brg_certificate → certificate}/tests/calibration/interval_test/interval_test.json +2 -2
  33. {brg_certificate → certificate}/tests/calibration/interval_test/interval_test.py +7 -6
  34. certificate/tests/calibration/output_power_test/output_power_test.json +22 -0
  35. certificate/tests/calibration/output_power_test/output_power_test.py +39 -0
  36. {brg_certificate → certificate}/tests/calibration/pattern_test/pattern_test.json +1 -1
  37. {brg_certificate → certificate}/tests/calibration/pattern_test/pattern_test.py +20 -15
  38. certificate/tests/cloud_connectivity/acl_ext_adv_test/acl_ext_adv_test.json +14 -0
  39. certificate/tests/cloud_connectivity/acl_ext_adv_test/acl_ext_adv_test.py +140 -0
  40. certificate/tests/cloud_connectivity/acl_test/acl_test.json +14 -0
  41. certificate/tests/cloud_connectivity/acl_test/acl_test.py +98 -0
  42. certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.json +18 -0
  43. certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.py +39 -0
  44. certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.json +18 -0
  45. certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.py +215 -0
  46. certificate/tests/cloud_connectivity/connection_test/connection_test.json +17 -0
  47. certificate/tests/cloud_connectivity/connection_test/connection_test.py +74 -0
  48. certificate/tests/cloud_connectivity/downlink_test/downlink_test.json +20 -0
  49. certificate/tests/cloud_connectivity/downlink_test/downlink_test.py +204 -0
  50. certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.json +16 -0
  51. certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.py +98 -0
  52. certificate/tests/cloud_connectivity/reboot_test/reboot_test.json +17 -0
  53. certificate/tests/cloud_connectivity/reboot_test/reboot_test.py +57 -0
  54. certificate/tests/cloud_connectivity/registration_test/registration_test.json +19 -0
  55. certificate/tests/cloud_connectivity/registration_test/registration_test.py +384 -0
  56. certificate/tests/cloud_connectivity/registration_test/registration_test_cli.py +90 -0
  57. certificate/tests/cloud_connectivity/stress_test/stress_test.json +16 -0
  58. certificate/tests/cloud_connectivity/stress_test/stress_test.py +97 -0
  59. certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.json +24 -0
  60. certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.py +93 -0
  61. certificate/tests/cloud_connectivity/uplink_test/uplink_test.json +19 -0
  62. certificate/tests/cloud_connectivity/uplink_test/uplink_test.py +161 -0
  63. {brg_certificate → certificate}/tests/datapath/aging_test/aging_test.json +1 -1
  64. certificate/tests/datapath/aging_test/aging_test.py +138 -0
  65. certificate/tests/datapath/event_ble5_test/event_ble5_test.json +16 -0
  66. certificate/tests/datapath/event_ble5_test/event_ble5_test.py +95 -0
  67. certificate/tests/datapath/event_test/event_test.json +16 -0
  68. certificate/tests/datapath/event_test/event_test.py +85 -0
  69. {brg_certificate → certificate}/tests/datapath/num_of_tags_test/num_of_tags_test.json +1 -1
  70. {brg_certificate → certificate}/tests/datapath/num_of_tags_test/num_of_tags_test.py +10 -8
  71. certificate/tests/datapath/output_power_test/output_power_test.json +22 -0
  72. {brg_certificate → certificate}/tests/datapath/output_power_test/output_power_test.py +17 -6
  73. {brg_certificate → certificate}/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.json +1 -1
  74. {brg_certificate → certificate}/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.py +10 -8
  75. {brg_certificate → certificate}/tests/datapath/pacer_interval_test/pacer_interval_test.json +1 -1
  76. {brg_certificate → certificate}/tests/datapath/pacer_interval_test/pacer_interval_test.py +9 -7
  77. {brg_certificate → certificate}/tests/datapath/pattern_test/pattern_test.json +2 -2
  78. {brg_certificate → certificate}/tests/datapath/pattern_test/pattern_test.py +18 -6
  79. certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.json +19 -0
  80. certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.py +61 -0
  81. {brg_certificate → certificate}/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.json +1 -1
  82. {brg_certificate → certificate}/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.py +11 -10
  83. certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.json +18 -0
  84. certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.py +83 -0
  85. {brg_certificate → certificate}/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.json +1 -1
  86. {brg_certificate → certificate}/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.py +10 -9
  87. {brg_certificate → certificate}/tests/datapath/pkt_filter_test/pkt_filter_test.json +1 -1
  88. {brg_certificate → certificate}/tests/datapath/pkt_filter_test/pkt_filter_test.py +10 -9
  89. {brg_certificate → certificate}/tests/datapath/rssi_threshold_test/rssi_threshold_test.json +1 -1
  90. {brg_certificate → certificate}/tests/datapath/rssi_threshold_test/rssi_threshold_test.py +9 -8
  91. brg_certificate/tests/datapath/output_power_test/output_power_test.json → certificate/tests/datapath/rx_channel_hopping_test/rx_channel_hopping_test.json +5 -4
  92. certificate/tests/datapath/rx_channel_hopping_test/rx_channel_hopping_test.py +77 -0
  93. {brg_certificate → certificate}/tests/datapath/rx_channel_test/rx_channel_test.json +2 -2
  94. {brg_certificate → certificate}/tests/datapath/rx_channel_test/rx_channel_test.py +7 -6
  95. {brg_certificate → certificate}/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.json +7 -7
  96. {brg_certificate → certificate}/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +112 -72
  97. {brg_certificate → certificate}/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.json +7 -7
  98. {brg_certificate → certificate}/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.py +112 -72
  99. {brg_certificate → certificate}/tests/datapath/stress_gen3_test/stress_gen3_test.json +3 -3
  100. {brg_certificate → certificate}/tests/datapath/stress_gen3_test/stress_gen3_test.py +12 -11
  101. {brg_certificate → certificate}/tests/datapath/stress_test/stress_test.json +3 -3
  102. {brg_certificate → certificate}/tests/datapath/stress_test/stress_test.py +12 -11
  103. {brg_certificate → certificate}/tests/datapath/tx_repetition_test/tx_repetition_test.json +2 -1
  104. {brg_certificate → certificate}/tests/datapath/tx_repetition_test/tx_repetition_test.py +14 -13
  105. certificate/tests/edge_mgmt/action_blink_test/action_blink_test.json +14 -0
  106. certificate/tests/edge_mgmt/action_blink_test/action_blink_test.py +24 -0
  107. certificate/tests/edge_mgmt/action_get_battery_sensor_test/action_get_battery_sensor_test.json +14 -0
  108. certificate/tests/edge_mgmt/action_get_battery_sensor_test/action_get_battery_sensor_test.py +43 -0
  109. certificate/tests/edge_mgmt/action_get_module_test/action_get_module_test.json +14 -0
  110. certificate/tests/edge_mgmt/action_get_module_test/action_get_module_test.py +42 -0
  111. certificate/tests/edge_mgmt/action_get_pof_data_test/action_get_pof_data_test.json +14 -0
  112. certificate/tests/edge_mgmt/action_get_pof_data_test/action_get_pof_data_test.py +44 -0
  113. certificate/tests/edge_mgmt/action_gw_hb_test/action_gw_hb_test.json +15 -0
  114. certificate/tests/edge_mgmt/action_gw_hb_test/action_gw_hb_test.py +42 -0
  115. certificate/tests/edge_mgmt/action_reboot_test/action_reboot_test.json +14 -0
  116. certificate/tests/edge_mgmt/action_reboot_test/action_reboot_test.py +49 -0
  117. certificate/tests/edge_mgmt/action_restore_defaults_test/action_restore_defaults_test.json +14 -0
  118. certificate/tests/edge_mgmt/action_restore_defaults_test/action_restore_defaults_test.py +102 -0
  119. certificate/tests/edge_mgmt/action_send_hb_test/action_send_hb_test.json +14 -0
  120. certificate/tests/edge_mgmt/action_send_hb_test/action_send_hb_test.py +41 -0
  121. {brg_certificate → certificate}/tests/edge_mgmt/periodic_msgs_test/periodic_msgs_test.json +2 -2
  122. {brg_certificate → certificate}/tests/edge_mgmt/periodic_msgs_test/periodic_msgs_test.py +22 -11
  123. {brg_certificate → certificate}/tests/energy2400/duty_cycle_test/duty_cycle_test.json +1 -1
  124. {brg_certificate → certificate}/tests/energy2400/duty_cycle_test/duty_cycle_test.py +7 -6
  125. certificate/tests/energy2400/output_power_test/output_power_test.json +22 -0
  126. {brg_certificate → certificate}/tests/energy2400/output_power_test/output_power_test.py +17 -6
  127. {brg_certificate → certificate}/tests/energy2400/pattern_test/pattern_test.json +1 -1
  128. {brg_certificate → certificate}/tests/energy2400/pattern_test/pattern_test.py +7 -6
  129. certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.json +25 -0
  130. certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +378 -0
  131. brg_certificate/tests/energy2400/signal_indicator_ble5_10_500k_test/signal_indicator_ble5_10_500k_test.json → certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.json +3 -3
  132. brg_certificate/tests/energy2400/signal_indicator_ble5_10_500k_test/signal_indicator_ble5_10_500k_test.py → certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.py +97 -87
  133. certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +23 -0
  134. certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +350 -0
  135. {brg_certificate → certificate}/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.json +1 -1
  136. {brg_certificate → certificate}/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.py +7 -6
  137. {brg_certificate → certificate}/tests/energy_sub1g/pattern_test/pattern_test.json +1 -1
  138. {brg_certificate → certificate}/tests/energy_sub1g/pattern_test/pattern_test.py +7 -6
  139. {brg_certificate → certificate}/tests/pwr_mgmt/pwr_mgmt_test/pwr_mgmt_test.json +1 -1
  140. {brg_certificate → certificate}/tests/pwr_mgmt/pwr_mgmt_test/pwr_mgmt_test.py +10 -10
  141. {brg_certificate → certificate}/tests/sensors/ext_sensor_test/ext_sensor_test.json +4 -4
  142. certificate/tests/sensors/ext_sensor_test/ext_sensor_test.py +455 -0
  143. certificate/wlt_types.py +122 -0
  144. {gw_certificate → common}/api_if/202/status.json +6 -0
  145. {gw_certificate → common}/api_if/203/status.json +6 -0
  146. {gw_certificate → common}/api_if/204/status.json +6 -0
  147. common/api_if/206/data.json +85 -0
  148. common/api_if/206/status.json +69 -0
  149. common/api_if/api_validation.py +85 -0
  150. common/web/templates/generator.html +148 -0
  151. common/web/templates/index.html +20 -0
  152. common/web/templates/menu.html +54 -0
  153. common/web/templates/parser.html +53 -0
  154. {brg_certificate/ag → common/web/templates}/wlt_types.html +1216 -191
  155. common/web/web_utils.py +377 -0
  156. {brg_certificate → common}/wltPb_pb2.py +14 -12
  157. {gw_certificate/common → common}/wltPb_pb2.pyi +16 -2
  158. gui_certificate/gui_certificate_cli.py +14 -0
  159. gui_certificate/server.py +1062 -0
  160. gui_certificate/templates/cert_run.html +1207 -0
  161. wiliot_certificate-4.5.0a1.dist-info/METADATA +110 -0
  162. wiliot_certificate-4.5.0a1.dist-info/RECORD +182 -0
  163. {wiliot_certificate-4.4.3.dist-info → wiliot_certificate-4.5.0a1.dist-info}/WHEEL +1 -1
  164. wiliot_certificate-4.5.0a1.dist-info/entry_points.txt +5 -0
  165. wiliot_certificate-4.5.0a1.dist-info/top_level.txt +3 -0
  166. brg_certificate/ag/energous_v0_defines.py +0 -925
  167. brg_certificate/ag/energous_v1_defines.py +0 -931
  168. brg_certificate/ag/energous_v2_defines.py +0 -925
  169. brg_certificate/ag/energous_v3_defines.py +0 -925
  170. brg_certificate/ag/energous_v4_defines.py +0 -925
  171. brg_certificate/ag/fanstel_lan_v0_defines.py +0 -925
  172. brg_certificate/ag/fanstel_lte_v0_defines.py +0 -925
  173. brg_certificate/ag/fanstel_wifi_v0_defines.py +0 -925
  174. brg_certificate/ag/minew_lte_v0_defines.py +0 -925
  175. brg_certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +0 -61
  176. brg_certificate/brg_certificate.py +0 -225
  177. brg_certificate/brg_certificate_cli.py +0 -63
  178. brg_certificate/cert_common.py +0 -923
  179. brg_certificate/cert_config.py +0 -402
  180. brg_certificate/cert_utils.py +0 -362
  181. brg_certificate/certificate_bcc_sanity_test_list.txt +0 -40
  182. brg_certificate/certificate_bcc_test_list.txt +0 -48
  183. brg_certificate/certificate_sanity_test_list.txt +0 -43
  184. brg_certificate/certificate_test_list.txt +0 -53
  185. brg_certificate/config/eclipse.json +0 -10
  186. brg_certificate/config/hivemq.json +0 -10
  187. brg_certificate/config/mosquitto.json +0 -10
  188. brg_certificate/config/mosquitto.md +0 -95
  189. brg_certificate/config/wiliot-dev.json +0 -10
  190. brg_certificate/restore_brg.py +0 -61
  191. brg_certificate/tests/calibration/output_power_test/output_power_test.json +0 -16
  192. brg_certificate/tests/calibration/output_power_test/output_power_test.py +0 -28
  193. brg_certificate/tests/datapath/aging_test/aging_test.py +0 -143
  194. brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.json +0 -16
  195. brg_certificate/tests/datapath/pacer_interval_tags_count_test/pacer_interval_tags_count_test.py +0 -73
  196. brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.json +0 -17
  197. brg_certificate/tests/datapath/tx_repetition_algo_test/tx_repetition_algo_test.py +0 -118
  198. brg_certificate/tests/edge_mgmt/actions_test/actions_test.json +0 -14
  199. brg_certificate/tests/edge_mgmt/actions_test/actions_test.py +0 -396
  200. brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.json +0 -20
  201. brg_certificate/tests/edge_mgmt/brg2brg_ota_ble5_test/brg2brg_ota_ble5_test.py +0 -94
  202. brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.json +0 -19
  203. brg_certificate/tests/edge_mgmt/brg2brg_ota_test/brg2brg_ota_test.py +0 -87
  204. brg_certificate/tests/edge_mgmt/leds_test/leds_test.json +0 -17
  205. brg_certificate/tests/edge_mgmt/leds_test/leds_test.py +0 -223
  206. brg_certificate/tests/edge_mgmt/ota_test/ota_test.json +0 -17
  207. brg_certificate/tests/edge_mgmt/ota_test/ota_test.py +0 -128
  208. brg_certificate/tests/energy2400/output_power_test/output_power_test.json +0 -16
  209. brg_certificate/tests/energy2400/signal_indicator_ble5_10_250k_test/signal_indicator_ble5_10_250k_test.json +0 -20
  210. brg_certificate/tests/energy2400/signal_indicator_ble5_10_250k_test/signal_indicator_ble5_10_250k_test.py +0 -321
  211. brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.json +0 -20
  212. brg_certificate/tests/energy2400/signal_indicator_sub1g_2_4_test/signal_indicator_sub1g_2_4_test.py +0 -141
  213. brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +0 -20
  214. brg_certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +0 -276
  215. brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.json +0 -20
  216. brg_certificate/tests/energy_sub1g/signal_indicator_functionality_test/signal_indicator_functionality_test.py +0 -390
  217. brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.json +0 -16
  218. brg_certificate/tests/energy_sub1g/signal_indicator_test/signal_indicator_test.py +0 -28
  219. brg_certificate/tests/sensors/ext_sensor_test/ext_sensor_test.py +0 -305
  220. brg_certificate/wltPb_pb2.pyi +0 -234
  221. brg_certificate/wlt_types.py +0 -113
  222. gw_certificate/ag/ut_defines.py +0 -364
  223. gw_certificate/ag/wlt_types.py +0 -85
  224. gw_certificate/ag/wlt_types_ag.py +0 -5310
  225. gw_certificate/ag/wlt_types_data.py +0 -64
  226. gw_certificate/api/extended_api.py +0 -23
  227. gw_certificate/api_if/200/data.json +0 -106
  228. gw_certificate/api_if/200/status.json +0 -47
  229. gw_certificate/api_if/201/data.json +0 -98
  230. gw_certificate/api_if/201/status.json +0 -53
  231. gw_certificate/api_if/205/logs.json +0 -12
  232. gw_certificate/api_if/api_validation.py +0 -38
  233. gw_certificate/api_if/gw_capabilities.py +0 -54
  234. gw_certificate/cert_results.py +0 -145
  235. gw_certificate/common/analysis_data_bricks.py +0 -60
  236. gw_certificate/common/debug.py +0 -42
  237. gw_certificate/common/serialization_formatter.py +0 -93
  238. gw_certificate/common/utils.py +0 -8
  239. gw_certificate/common/utils_defines.py +0 -15
  240. gw_certificate/common/wltPb_pb2.py +0 -84
  241. gw_certificate/gw_certificate.py +0 -154
  242. gw_certificate/gw_certificate_cli.py +0 -87
  243. gw_certificate/interface/4.4.93_app.zip +0 -0
  244. gw_certificate/interface/4.4.93_sd_bl_app.zip +0 -0
  245. gw_certificate/interface/ble_simulator.py +0 -61
  246. gw_certificate/interface/ble_sniffer.py +0 -189
  247. gw_certificate/interface/flash_fw.py +0 -90
  248. gw_certificate/interface/if_defines.py +0 -36
  249. gw_certificate/interface/mqtt.py +0 -563
  250. gw_certificate/interface/nrfutil-linux +0 -0
  251. gw_certificate/interface/nrfutil-mac +0 -0
  252. gw_certificate/interface/nrfutil.exe +0 -0
  253. gw_certificate/interface/pkt_generator.py +0 -594
  254. gw_certificate/interface/uart_if.py +0 -236
  255. gw_certificate/interface/uart_ports.py +0 -20
  256. gw_certificate/templates/results.html +0 -241
  257. gw_certificate/templates/stage.html +0 -22
  258. gw_certificate/templates/table.html +0 -6
  259. gw_certificate/templates/test.html +0 -38
  260. gw_certificate/tests/__init__.py +0 -10
  261. gw_certificate/tests/actions.py +0 -289
  262. gw_certificate/tests/bad_crc_to_PER_quantization.csv +0 -51
  263. gw_certificate/tests/connection.py +0 -188
  264. gw_certificate/tests/downlink.py +0 -172
  265. gw_certificate/tests/generic.py +0 -238
  266. gw_certificate/tests/registration.py +0 -340
  267. gw_certificate/tests/static/__init__.py +0 -0
  268. gw_certificate/tests/static/connection_defines.py +0 -9
  269. gw_certificate/tests/static/downlink_defines.py +0 -9
  270. gw_certificate/tests/static/generated_packet_table.py +0 -195
  271. gw_certificate/tests/static/packet_table.csv +0 -10067
  272. gw_certificate/tests/static/references.py +0 -5
  273. gw_certificate/tests/static/uplink_defines.py +0 -14
  274. gw_certificate/tests/throughput.py +0 -240
  275. gw_certificate/tests/uplink.py +0 -853
  276. wiliot_certificate-4.4.3.dist-info/METADATA +0 -211
  277. wiliot_certificate-4.4.3.dist-info/RECORD +0 -210
  278. wiliot_certificate-4.4.3.dist-info/entry_points.txt +0 -3
  279. wiliot_certificate-4.4.3.dist-info/top_level.txt +0 -3
  280. {brg_certificate → certificate}/__init__.py +0 -0
  281. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/side_info_sensor.json +0 -0
  282. {brg_certificate → certificate}/ag/wlt_types_ag_jsons/signal_indicator_data.json +0 -0
  283. {gw_certificate → common}/api_if/202/data.json +0 -0
  284. {gw_certificate/api_if/200 → common/api_if/202}/logs.json +0 -0
  285. {gw_certificate → common}/api_if/203/data.json +0 -0
  286. {gw_certificate/api_if/201 → common/api_if/203}/logs.json +0 -0
  287. {gw_certificate → common}/api_if/204/data.json +0 -0
  288. {gw_certificate/api_if/202 → common/api_if/204}/logs.json +0 -0
  289. {gw_certificate → common}/api_if/205/data.json +0 -0
  290. {gw_certificate/api_if/203 → common/api_if/205}/logs.json +0 -0
  291. {gw_certificate → common}/api_if/205/status.json +0 -0
  292. {gw_certificate/api_if/204 → common/api_if/206}/logs.json +0 -0
  293. {gw_certificate → common/api_if}/__init__.py +0 -0
  294. {gw_certificate/api_if → gui_certificate}/__init__.py +0 -0
  295. {wiliot_certificate-4.4.3.dist-info → wiliot_certificate-4.5.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1062 @@
1
+ import os
2
+ import json
3
+ import tempfile
4
+ from flask import Flask, render_template, request, session, redirect, url_for, flash, send_file, jsonify
5
+ from jinja2 import ChoiceLoader, PackageLoader
6
+ from werkzeug.utils import secure_filename
7
+ from certificate.certificate_cli import CertificateCLI
8
+ from certificate.cert_defines import *
9
+ import certificate.cert_utils as cert_utils
10
+ import common.web.web_utils as web_utils
11
+ import serial.tools.list_ports
12
+
13
+ # Certificate Run Defines
14
+ DATA_REAL_TAGS = "REAL"
15
+ DATA_SIMULATION = "SIM"
16
+ TEST_LIST_DEFAULT_FILE = "certificate_sanity_test_list.txt"
17
+ PREFERRED_TERMINAL = os.getenv("CLI_TERMINAL", "auto")
18
+ CERT_TESTS_ROOT = os.path.join(BASE_DIR, "tests")
19
+
20
+ # Module Names
21
+ MODULE_CLOUD_CONNECTIVITY = "cloud_connectivity"
22
+ MODULE_EDGE_MGMT = "edge_mgmt"
23
+
24
+ # Flavor constants
25
+ FLAVOR_GW_ONLY = "gw_only"
26
+ FLAVOR_BRIDGE_ONLY = "bridge_only"
27
+ FLAVOR_COMBO = "combo"
28
+
29
+ # Upload configuration - use system temp directory
30
+ TEMP_BASE = tempfile.gettempdir()
31
+ UPLOAD_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_uploaded_schemas")
32
+ CONFIG_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_saved_configs")
33
+ TEST_LIST_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_test_lists")
34
+ ALLOWED_EXTENSIONS = {'json'}
35
+ MAX_UPLOAD_SIZE = 16 * 1024 * 1024 # 16MB
36
+
37
+ app = Flask(__name__)
38
+ app.secret_key = os.urandom(24) # Required for sessions
39
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
40
+ app.config['MAX_CONTENT_LENGTH'] = MAX_UPLOAD_SIZE
41
+
42
+ # Create folders if they don't exist
43
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
44
+ os.makedirs(CONFIG_FOLDER, exist_ok=True)
45
+ os.makedirs(TEST_LIST_FOLDER, exist_ok=True)
46
+
47
+ # extend Jinja search path to include shared dir
48
+ app.jinja_loader = ChoiceLoader([
49
+ app.jinja_loader,
50
+ PackageLoader("common.web", "templates"),
51
+ ])
52
+ # Update jinja_env globals
53
+ app.jinja_env.globals.update(
54
+ FDM=web_utils.FDM,
55
+ CERT_WEB=web_utils.CERT_WEB,
56
+ **web_utils.GLOBAL_DEFINES_TO_UPDATE
57
+ )
58
+
59
+ def allowed_file(filename):
60
+ """Check if file extension is allowed."""
61
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
62
+
63
+ def get_mandatory_modules_for_flavor(flavor):
64
+ """Get mandatory modules for a given flavor."""
65
+ if flavor == FLAVOR_GW_ONLY:
66
+ return [MODULE_CLOUD_CONNECTIVITY]
67
+ elif flavor == FLAVOR_BRIDGE_ONLY:
68
+ return [MODULE_EDGE_MGMT] # Interface module
69
+ elif flavor == FLAVOR_COMBO:
70
+ return [MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT]
71
+ return []
72
+
73
+ # TODO: This is a temporary mapping to map test module names to schema module names - remove this
74
+ def test_module_to_schema_module(test_module_name):
75
+ """Map test module names to validation schema module names."""
76
+ mapping = {
77
+ "calibration": "calibration",
78
+ "datapath": "datapath",
79
+ "energy2400": "energy2400",
80
+ "energy_sub1g": "energySub1g", # snake_case to camelCase
81
+ "pwr_mgmt": "powerManagement", # abbreviated to full camelCase
82
+ "sensors": "externalSensor", # different name
83
+ "custom": "custom"
84
+ }
85
+ return mapping.get(test_module_name, test_module_name) # Return as-is if not in mapping
86
+
87
+ def filter_modules_by_flavor(tests_schema, flavor, schema_data=None):
88
+ """Filter modules and tests based on selected flavor."""
89
+ mandatory_modules = get_mandatory_modules_for_flavor(flavor)
90
+ filtered_modules = []
91
+
92
+ # Get schema module order if available
93
+ schema_module_order = []
94
+ if schema_data and "modules" in schema_data:
95
+ schema_module_order = list(schema_data["modules"].keys())
96
+
97
+ for mod in tests_schema.get("modules", []):
98
+ mod_name = mod.get("name", "")
99
+ filtered_tests = []
100
+
101
+ for test in mod.get("tests", []):
102
+ test_meta = test.get("meta", {})
103
+ gw_only_test = test_meta.get("gwOnlyTest", 0)
104
+
105
+ # Filter tests based on flavor
106
+ if flavor == FLAVOR_GW_ONLY:
107
+ # For GW only, include tests marked as gwOnlyTest or from cloud_connectivity module
108
+ if gw_only_test == 1 or mod_name == MODULE_CLOUD_CONNECTIVITY:
109
+ filtered_tests.append(test)
110
+ elif flavor == FLAVOR_BRIDGE_ONLY:
111
+ # For Bridge only, exclude GW-only tests (but include all others)
112
+ if gw_only_test == 0:
113
+ filtered_tests.append(test)
114
+ elif flavor == FLAVOR_COMBO:
115
+ # Include all tests for combo
116
+ filtered_tests.append(test)
117
+
118
+ # Only include module if it has tests or is mandatory
119
+ # For mandatory modules, include even if no tests match (they'll be shown but empty)
120
+ if filtered_tests or mod_name in mandatory_modules:
121
+ mod_copy = mod.copy()
122
+ mod_copy["tests"] = filtered_tests
123
+ mod_copy["is_mandatory"] = mod_name in mandatory_modules
124
+ filtered_modules.append(mod_copy)
125
+
126
+ # Sort modules: cloud_connectivity first, then edge_mgmt, then by schema order
127
+ def module_sort_key(mod):
128
+ mod_name = mod.get("name", "")
129
+ # cloud_connectivity always first
130
+ if mod_name == MODULE_CLOUD_CONNECTIVITY:
131
+ return (0, "")
132
+ # edge_mgmt always first among bridge modules
133
+ if mod_name == MODULE_EDGE_MGMT:
134
+ return (1, "")
135
+ # Then modules in schema order
136
+ if schema_module_order:
137
+ try:
138
+ # Map test module name to schema module name
139
+ schema_mod_name = test_module_to_schema_module(mod_name)
140
+ idx = schema_module_order.index(schema_mod_name)
141
+ return (2, idx)
142
+ except ValueError:
143
+ pass
144
+ return (3, mod_name)
145
+
146
+ filtered_modules.sort(key=module_sort_key)
147
+
148
+ return filtered_modules
149
+
150
+ def get_mandatory_tests_for_modules(modules):
151
+ """Get list of mandatory test IDs from selected modules."""
152
+ mandatory_tests = []
153
+ for mod in modules:
154
+ for test in mod.get("tests", []):
155
+ if test.get("meta", {}).get("mandatory", 0) == 1:
156
+ mandatory_tests.append(test.get("id"))
157
+ return mandatory_tests
158
+
159
+ def _check_missing_defaults(properties, path_prefix=""):
160
+ """
161
+ Recursively check if all properties have default values.
162
+ Returns list of field paths missing defaults.
163
+ Properties with nested 'properties' (object types) don't need defaults themselves,
164
+ only their nested properties need defaults.
165
+ Array types with 'items' field also don't need defaults.
166
+ """
167
+ missing_defaults = []
168
+
169
+ if not isinstance(properties, dict):
170
+ return missing_defaults
171
+
172
+ for field_name, field_def in properties.items():
173
+ if not isinstance(field_def, dict):
174
+ continue
175
+
176
+ current_path = f"{path_prefix}.{field_name}" if path_prefix else field_name
177
+
178
+ # Check if this field has nested properties
179
+ has_nested_properties = "properties" in field_def and isinstance(field_def["properties"], dict)
180
+
181
+ # Check if this is an array type with items
182
+ is_array_with_items = field_def.get("type") == "array" and "items" in field_def
183
+
184
+ # Only check for default if this property doesn't have nested properties and isn't an array with items
185
+ # (Objects with nested properties and arrays with items don't need defaults themselves)
186
+ if not has_nested_properties and not is_array_with_items and "default" not in field_def:
187
+ missing_defaults.append(current_path)
188
+
189
+ # Recursively check nested properties if they exist
190
+ if has_nested_properties:
191
+ nested_missing = _check_missing_defaults(field_def["properties"], current_path)
192
+ missing_defaults.extend(nested_missing)
193
+
194
+ return missing_defaults
195
+
196
+ def validate_schema_file(filepath):
197
+ """Validate the uploaded schema file."""
198
+ try:
199
+ with open(filepath, "r", encoding="utf-8") as f:
200
+ data = json.load(f)
201
+
202
+ # Basic validation
203
+ if "properties" not in data and "modules" not in data:
204
+ return False, "Schema missing both 'properties' and 'modules' keys"
205
+
206
+ # Check for cloud_connectivity_flag or brg_flag
207
+ has_cloud = cert_utils.cloud_connectivity_flag(data)
208
+ has_brg = cert_utils.brg_flag(data)
209
+
210
+ if not (has_cloud or has_brg):
211
+ return False, "Schema must support either cloud connectivity or bridge device"
212
+
213
+ # Check for missing defaults in all fields
214
+ missing_defaults = []
215
+
216
+ # Check top-level properties (gateway properties)
217
+ if "properties" in data and isinstance(data["properties"], dict):
218
+ missing_defaults.extend(_check_missing_defaults(data["properties"], ""))
219
+
220
+ # Check module properties (bridge modules)
221
+ if "modules" in data and isinstance(data["modules"], dict):
222
+ for module_name, module_def in data["modules"].items():
223
+ if isinstance(module_def, dict) and "properties" in module_def:
224
+ module_missing = _check_missing_defaults(module_def["properties"], f"modules.{module_name}")
225
+ missing_defaults.extend(module_missing)
226
+
227
+ if missing_defaults:
228
+ # Format error message with clear list of missing fields
229
+ if len(missing_defaults) == 1:
230
+ return False, f"Field '{missing_defaults[0]}' is missing a 'default' value"
231
+ else:
232
+ missing_list = ", ".join(missing_defaults)
233
+ return False, f"The following {len(missing_defaults)} fields are missing 'default' values: {missing_list}"
234
+
235
+ return True, data
236
+ except json.JSONDecodeError as e:
237
+ return False, f"Invalid JSON: {e}"
238
+ except Exception as e:
239
+ return False, f"Error reading file: {e}"
240
+
241
+ def load_schema_data(schema_path):
242
+ """Load schema data from file path. Returns None if file doesn't exist or can't be read."""
243
+ if not schema_path:
244
+ return None
245
+ try:
246
+ schema_path = os.path.normpath(str(schema_path).strip().strip('"').strip("'"))
247
+ if os.path.exists(schema_path):
248
+ with open(schema_path, "r", encoding="utf-8") as f:
249
+ return json.load(f)
250
+ except Exception:
251
+ pass
252
+ return None
253
+
254
+ def verify_schema_matches_selection(schema_data, flavor, selected_modules, selected_tests):
255
+ """Verify that the validation schema matches the selected flavor/modules/tests."""
256
+ errors = []
257
+ warnings = []
258
+
259
+ has_cloud = cert_utils.cloud_connectivity_flag(schema_data)
260
+ has_brg = cert_utils.brg_flag(schema_data)
261
+
262
+ # Check flavor compatibility
263
+ if flavor == FLAVOR_GW_ONLY and not has_cloud:
264
+ errors.append("Schema does not support Gateway (cloud connectivity) but GW only flavor selected")
265
+ elif flavor == FLAVOR_BRIDGE_ONLY and not has_brg:
266
+ errors.append("Schema does not support Bridge but Bridge only flavor selected")
267
+ elif flavor == FLAVOR_COMBO:
268
+ if not has_cloud and not has_brg:
269
+ errors.append("Schema does not support Gateway or Bridge for Combo flavor")
270
+ elif not has_cloud:
271
+ warnings.append("Schema missing Gateway support for Combo flavor")
272
+ elif not has_brg:
273
+ warnings.append("Schema missing Bridge support for Combo flavor")
274
+
275
+ # Check module support (if schema has modules field)
276
+ # The schema modules dict contains the bridge modules that the device supports
277
+ # Note: cloud_connectivity is a gateway feature, not a bridge module, so it won't be in schema modules
278
+ # Note: edge_mgmt is not a configurable module in schema
279
+ # Only check bridge modules that are configurable in the schema
280
+ if has_brg and "modules" in schema_data:
281
+ schema_modules = schema_data.get("modules", {})
282
+ if isinstance(schema_modules, dict):
283
+ schema_module_names = set(schema_modules.keys())
284
+ # Only check bridge modules that are configurable (exclude edge_mgmt and cloud_connectivity)
285
+ bridge_modules_to_check = [m for m in selected_modules if m not in (MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT)]
286
+ for test_mod_name in bridge_modules_to_check:
287
+ # Map test module name to schema module name
288
+ schema_mod_name = test_module_to_schema_module(test_mod_name)
289
+ if schema_mod_name not in schema_module_names:
290
+ warnings.append(f"Module '{test_mod_name}' may not be supported according to the uploaded schema")
291
+
292
+ return len(errors) == 0, errors, warnings
293
+
294
+ def check_certification_status(flavor, selected_modules, selected_tests, tests_schema):
295
+ """Check if the selection qualifies for certification or is test-only."""
296
+ mandatory_modules = get_mandatory_modules_for_flavor(flavor)
297
+ all_mandatory_tests = []
298
+
299
+ # Get all mandatory tests from mandatory modules
300
+ for mod in tests_schema.get("modules", []):
301
+ mod_name = mod.get("name", "")
302
+ if mod_name in mandatory_modules:
303
+ for test in mod.get("tests", []):
304
+ if test.get("meta", {}).get("mandatory", 0) == 1:
305
+ all_mandatory_tests.append(test.get("id"))
306
+
307
+ # Check if all mandatory modules are selected
308
+ selected_module_names = [m.get("name") if isinstance(m, dict) else m for m in selected_modules]
309
+ missing_modules = [m for m in mandatory_modules if m not in selected_module_names]
310
+
311
+ # Check if all mandatory tests are selected
312
+ missing_tests = [t for t in all_mandatory_tests if t not in selected_tests]
313
+
314
+ # Check for "at least one additional module" requirement for Bridge Only and Combo
315
+ missing_additional_module = False
316
+ if flavor in (FLAVOR_BRIDGE_ONLY, FLAVOR_COMBO):
317
+ # Count non-mandatory selected modules (exclude cloud_connectivity and edge_mgmt)
318
+ additional_modules = [m for m in selected_module_names if m not in (MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT)]
319
+ if len(additional_modules) == 0:
320
+ missing_additional_module = True
321
+
322
+ is_certified = len(missing_modules) == 0 and len(missing_tests) == 0 and not missing_additional_module
323
+
324
+ return is_certified, missing_modules, missing_tests, missing_additional_module
325
+
326
+ def _prepare_form_data_for_template(session, cert_schema):
327
+ """Prepare form_data for template, ensuring custom_broker_path is included if available."""
328
+ form_data = session.get('form_data', {}).copy()
329
+ custom_broker_path = session.get('custom_broker_path')
330
+ if custom_broker_path:
331
+ custom_broker_field_name = f"{cert_schema['title']}_custom_broker"
332
+ if custom_broker_field_name not in form_data:
333
+ form_data[custom_broker_field_name] = custom_broker_path
334
+ return form_data
335
+
336
+ def form_to_argv(form, parser, title) -> list[str]:
337
+ """
338
+ Map POSTed form fields back into argv tokens the parser expects.
339
+ - For flags (store_true/false): include the flag when checked.
340
+ - For text/select: include '--opt value' only if non-empty (keeps defaults otherwise).
341
+ - Special-case overwrite_defaults: accept textarea of key=value lines -> join to key=value,... string.
342
+ """
343
+ argv: list[str] = []
344
+ for a in parser._actions:
345
+ from argparse import _HelpAction, _VersionAction, _StoreTrueAction, _StoreFalseAction
346
+ if isinstance(a, (_HelpAction, _VersionAction)) or a.dest in ('tl', 'validation_schema'):
347
+ continue
348
+
349
+ dest = f'{title}_{a.dest}'
350
+ opt = a.option_strings and max(a.option_strings, key=len) # prefer long option
351
+ val = form.get(dest, "")
352
+
353
+ # Special handling: overwrite_defaults textarea to csv string
354
+ if "overwrite_defaults" in dest:
355
+ # allow either CSV or line-per-pair; normalize to CSV for parser type fn
356
+ lines = [ln.strip().replace(" ", "") for ln in form.get(dest, "").splitlines() if ln.strip()]
357
+ if lines:
358
+ val = ",".join(lines)
359
+
360
+ if getattr(a, "choices", None) and a.nargs in ("+", "*"):
361
+ vals = form.getlist(dest + "[]")
362
+ if vals:
363
+ argv += [opt] + vals
364
+ continue
365
+
366
+ # store_true / store_false
367
+ if isinstance(a, _StoreTrueAction):
368
+ if form.get(dest): # checkbox "on"
369
+ argv += [opt]
370
+ continue
371
+ if isinstance(a, _StoreFalseAction):
372
+ if form.get(dest):
373
+ argv += [opt]
374
+ continue
375
+
376
+ # Positionals (you have none) vs optionals (you have only optionals)
377
+ if a.option_strings:
378
+ if val not in ("", None):
379
+ argv += [opt, val]
380
+ else:
381
+ if val not in ("", None):
382
+ argv += [val]
383
+ return argv
384
+
385
+ def _sort_tests_by_module_priority(test_ids: list[str]) -> list[str]:
386
+ """
387
+ Sort test IDs to ensure edge_mgmt tests come first (after cloud_connectivity if present).
388
+ Test ID format: 'module/test_name'
389
+ """
390
+ def test_sort_key(test_id: str):
391
+ module = test_id.split('/')[0] if '/' in test_id else test_id
392
+ # cloud_connectivity first
393
+ if module == MODULE_CLOUD_CONNECTIVITY:
394
+ return (0, test_id)
395
+ # edge_mgmt second
396
+ if module == MODULE_EDGE_MGMT:
397
+ return (1, test_id)
398
+ # Others last
399
+ return (2, test_id)
400
+
401
+ return sorted(test_ids, key=test_sort_key)
402
+
403
+ def _write_testlist(selected_ids: list[str], form_data=None, is_certified=False) -> str:
404
+ """
405
+ Create a temp test list file in the temp directory. If your CLI expects script paths, write id_to_py[tid].
406
+ If it expects logical names, write 'module/test'. Adjust once here.
407
+ """
408
+ # Sort tests to ensure edge_mgmt comes first
409
+ sorted_test_ids = _sort_tests_by_module_priority(selected_ids)
410
+
411
+ test_list = f"custom_tl_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.txt"
412
+ path = os.path.join(TEST_LIST_FOLDER, test_list)
413
+ with open(path, 'w') as f:
414
+ # Add comment about certification status
415
+ if not is_certified:
416
+ f.write("# NON-CERTIFYING RUN (mandatory tests/modules missing)\n")
417
+ for test_id in sorted_test_ids:
418
+ tid_safe = test_id.replace('/', '_')
419
+ param_key = f"params_{tid_safe}[]"
420
+ params = form_data.getlist(param_key) if form_data else [] # list[str]
421
+ # Example line format: "<module/test> <param1> <param2> ..."
422
+ line = " ".join([test_id] + params)
423
+ f.write(line + "\n")
424
+ print(f"Custom test list saved in {path}")
425
+ return path
426
+
427
+ @app.route("/")
428
+ def index():
429
+ """Redirect to step 1 or show current step."""
430
+ if 'current_step' not in session:
431
+ session['current_step'] = 1
432
+ session['flavor'] = None
433
+ session['schema_path'] = None
434
+ session['selected_modules'] = []
435
+ session['selected_tests'] = []
436
+ session['form_data'] = {}
437
+ return redirect(url_for('step', step_num=session.get('current_step', 1)))
438
+
439
+ @app.route("/step/<int:step_num>", methods=['GET', 'POST'])
440
+ def step(step_num):
441
+ """Handle multi-step wizard navigation."""
442
+ if step_num < 1 or step_num > 5:
443
+ flash("Invalid step number", "error")
444
+ return redirect(url_for('index'))
445
+
446
+ # Initialize session if needed
447
+ if 'current_step' not in session:
448
+ session['current_step'] = 1
449
+ session['flavor'] = None
450
+ session['schema_path'] = None
451
+ session['selected_modules'] = []
452
+ session['selected_tests'] = []
453
+ session['form_data'] = {}
454
+
455
+ title = "Run Certificate"
456
+ tests_schema = web_utils.scan_tests_dir(CERT_TESTS_ROOT)
457
+ error = None
458
+ warning = None
459
+
460
+ if request.method == "POST":
461
+ action = request.form.get('action', 'next')
462
+
463
+ if action == 'back':
464
+ # For GW only, skip step 3 when going back from step 4
465
+ if step_num == 4 and session.get('flavor') == FLAVOR_GW_ONLY:
466
+ session['current_step'] = 2
467
+ else:
468
+ session['current_step'] = max(1, step_num - 1)
469
+ return redirect(url_for('step', step_num=session['current_step']))
470
+
471
+ # Step 1: Flavor selection
472
+ if step_num == 1:
473
+ flavor = request.form.get('flavor')
474
+ if flavor in [FLAVOR_GW_ONLY, FLAVOR_BRIDGE_ONLY, FLAVOR_COMBO]:
475
+ session['flavor'] = flavor
476
+ # Initialize mandatory modules only if not already set (preserves user selections)
477
+ if 'selected_modules' not in session or not session.get('selected_modules'):
478
+ mandatory_modules = get_mandatory_modules_for_flavor(flavor)
479
+ session['selected_modules'] = mandatory_modules
480
+ session['current_step'] = 2
481
+ return redirect(url_for('step', step_num=2))
482
+ else:
483
+ error = "Please select a flavor"
484
+
485
+ # Step 2: Validation schema upload
486
+ elif step_num == 2:
487
+ # Only validate file if moving forward (not going back)
488
+ if action == 'next':
489
+ if 'schema_file' in request.files:
490
+ file = request.files['schema_file']
491
+ if file and file.filename and allowed_file(file.filename):
492
+ filename = secure_filename(file.filename)
493
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
494
+ filename = f"{timestamp}_{filename}"
495
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
496
+ file.save(filepath)
497
+
498
+ is_valid, result = validate_schema_file(filepath)
499
+ if is_valid:
500
+ # Store normalized path without quotes (don't store full schema_data to avoid cookie size issues)
501
+ normalized_path = os.path.normpath(filepath)
502
+ session['schema_path'] = normalized_path
503
+ # Don't store schema_data in session - load from file when needed
504
+ # Initialize mandatory modules only if not already set (preserves user selections)
505
+ flavor = session.get('flavor')
506
+ if 'selected_modules' not in session or not session.get('selected_modules'):
507
+ mandatory_modules = get_mandatory_modules_for_flavor(flavor)
508
+ session['selected_modules'] = mandatory_modules
509
+ # For GW only, skip step 3 (modules)
510
+ if session.get('flavor') == FLAVOR_GW_ONLY:
511
+ session['current_step'] = 4
512
+ return redirect(url_for('step', step_num=4))
513
+ else:
514
+ session['current_step'] = 3
515
+ return redirect(url_for('step', step_num=3))
516
+ else:
517
+ error = f"Schema validation failed: {result}"
518
+ os.remove(filepath)
519
+ else:
520
+ error = "Please upload a valid JSON schema file"
521
+ else:
522
+ error = "Please select a validation schema file"
523
+
524
+ # Step 3: Module selection
525
+ elif step_num == 3:
526
+ # Skip step 3 for GW only (shouldn't reach here, but handle gracefully)
527
+ if session.get('flavor') == FLAVOR_GW_ONLY:
528
+ mandatory_modules = get_mandatory_modules_for_flavor(FLAVOR_GW_ONLY)
529
+ session['selected_modules'] = mandatory_modules
530
+ session['current_step'] = 4
531
+ return redirect(url_for('step', step_num=4))
532
+
533
+ selected_modules = request.form.getlist('modules[]')
534
+
535
+ # Get mandatory modules for this flavor
536
+ flavor = session.get('flavor')
537
+ mandatory_modules = get_mandatory_modules_for_flavor(flavor) if flavor else []
538
+
539
+ # Use submitted modules directly (mandatory modules can now be deselected)
540
+ all_selected_modules = selected_modules
541
+
542
+ # Allow moving forward even if requirements aren't met (warnings shown via JavaScript)
543
+ if all_selected_modules:
544
+ session['selected_modules'] = all_selected_modules
545
+ session['current_step'] = 4
546
+ return redirect(url_for('step', step_num=4))
547
+
548
+ # Step 4: Test selection
549
+ elif step_num == 4:
550
+ selected_tests = request.form.getlist('tests[]')
551
+
552
+ # Get mandatory tests from selected modules
553
+ flavor = session.get('flavor')
554
+ selected_modules = session.get('selected_modules', [])
555
+ schema_path = session.get('schema_path')
556
+ schema_data = load_schema_data(schema_path) if schema_path else None
557
+ filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data) if flavor else []
558
+
559
+ # Find all mandatory tests from selected modules
560
+ mandatory_tests = []
561
+ for mod in filtered_modules:
562
+ if mod.get('name') in selected_modules:
563
+ for test in mod.get('tests', []):
564
+ if test.get('meta', {}).get('mandatory', 0) == 1:
565
+ mandatory_tests.append(test.get('id'))
566
+
567
+ # Use submitted tests directly (mandatory tests can now be deselected)
568
+ all_selected_tests = selected_tests
569
+
570
+ # Store all form data including CLI parameters and test parameters
571
+ form_data_dict = {}
572
+ for key in request.form:
573
+ if key.endswith('[]'):
574
+ form_data_dict[key] = request.form.getlist(key)
575
+ else:
576
+ form_data_dict[key] = request.form.get(key)
577
+
578
+ # Handle custom_broker file upload
579
+ cert_cli = CertificateCLI()
580
+ cert_schema = web_utils.parser_to_schema(cert_cli.parser)
581
+ custom_broker_field_name = f"{cert_schema['title']}_custom_broker"
582
+ custom_broker_file_key = f"{custom_broker_field_name}_file"
583
+
584
+ # Preserve existing custom_broker_path if no new file is uploaded
585
+ existing_custom_broker_path = session.get('custom_broker_path')
586
+ if existing_custom_broker_path:
587
+ form_data_dict[custom_broker_field_name] = existing_custom_broker_path
588
+
589
+ if custom_broker_file_key in request.files:
590
+ file = request.files[custom_broker_file_key]
591
+ if file and file.filename and allowed_file(file.filename):
592
+ filename = secure_filename(file.filename)
593
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
594
+ filename = f"{timestamp}_{filename}"
595
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
596
+ file.save(filepath)
597
+
598
+ # Validate file is valid JSON
599
+ try:
600
+ with open(filepath, "r", encoding="utf-8") as f:
601
+ json.load(f)
602
+ # Store normalized path
603
+ normalized_path = os.path.normpath(filepath)
604
+ session['custom_broker_path'] = normalized_path
605
+ form_data_dict[custom_broker_field_name] = normalized_path
606
+ except json.JSONDecodeError as e:
607
+ error = f"Invalid JSON in custom broker file: {e}"
608
+ os.remove(filepath)
609
+ except Exception as e:
610
+ error = f"Error reading custom broker file: {e}"
611
+ if os.path.exists(filepath):
612
+ os.remove(filepath)
613
+ elif file and file.filename:
614
+ error = "Custom broker file must be a JSON file"
615
+
616
+ # Validate all required fields (required for moving forward, but allow going back)
617
+ if action == 'next':
618
+ # Get required fields from CLI schema
619
+ cert_cli = CertificateCLI()
620
+ cert_schema = web_utils.parser_to_schema(cert_cli.parser)
621
+ required_fields = []
622
+ missing_required = []
623
+
624
+ # Find all required fields (excluding validation_schema and tl which are handled separately)
625
+ for field in cert_schema.get('fields', []):
626
+ if field.get('required') and field.get('name') not in ('validation_schema', 'tl'):
627
+ required_fields.append(field)
628
+
629
+ # Check each required field
630
+ for field in required_fields:
631
+ field_name = f"{cert_schema['title']}_{field['name']}"
632
+ field_value = form_data_dict.get(field_name, '')
633
+
634
+ # Special handling for custom_broker file upload
635
+ if field['name'] == 'custom_broker':
636
+ # Check if file was uploaded or path exists in session
637
+ custom_broker_file_key = f"{field_name}_file"
638
+ if custom_broker_file_key not in request.files or not request.files[custom_broker_file_key].filename:
639
+ # Check if path exists in form_data or session
640
+ if not field_value and not session.get('custom_broker_path'):
641
+ missing_required.append(field.get('name', field['label']))
642
+ else:
643
+ # For other fields, check if value is provided
644
+ if not field_value or (isinstance(field_value, str) and not field_value.strip()):
645
+ missing_required.append(field.get('name', field['label']))
646
+
647
+ if missing_required:
648
+ if len(missing_required) == 1:
649
+ error = f"Please provide {missing_required[0]} to continue"
650
+ else:
651
+ error = f"Please provide the following required fields to continue: {', '.join(missing_required)}"
652
+ else:
653
+ # Only save and move forward if all required fields are provided
654
+ if all_selected_tests:
655
+ session['selected_tests'] = all_selected_tests
656
+ session['form_data'] = form_data_dict
657
+ session['current_step'] = 5
658
+ return redirect(url_for('step', step_num=5))
659
+ else:
660
+ # Going back - save form data but don't validate required fields
661
+ if all_selected_tests:
662
+ session['selected_tests'] = all_selected_tests
663
+ session['form_data'] = form_data_dict
664
+
665
+ # Step 5: Review and run
666
+ elif step_num == 5:
667
+ if request.form.get('action') == 'run':
668
+ return execute_certificate()
669
+ elif request.form.get('action') == 'edit':
670
+ edit_step = int(request.form.get('edit_step', 1))
671
+ session['current_step'] = edit_step
672
+ return redirect(url_for('step', step_num=edit_step))
673
+
674
+ # Prepare data for template
675
+ flavor = session.get('flavor')
676
+ schema_path = session.get('schema_path')
677
+ selected_modules = session.get('selected_modules', [])
678
+ selected_tests = session.get('selected_tests', [])
679
+
680
+ # Initialize mandatory items only if not already in session (new session or after "Start New Run")
681
+ # This ensures mandatory items are only pre-checked at the beginning
682
+ if flavor and not selected_modules:
683
+ mandatory_modules = get_mandatory_modules_for_flavor(flavor)
684
+ selected_modules = mandatory_modules
685
+ session['selected_modules'] = selected_modules
686
+
687
+ # Initialize mandatory tests only if not already in session and we have selected modules
688
+ # Only initialize when first reaching step 4 (not on every GET request)
689
+ if flavor and selected_modules and not selected_tests and step_num == 4:
690
+ schema_path = session.get('schema_path')
691
+ schema_data = load_schema_data(schema_path) if schema_path else None
692
+ filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data) if flavor else []
693
+ mandatory_tests = []
694
+ for mod in filtered_modules:
695
+ if mod.get('name') in selected_modules:
696
+ for test in mod.get('tests', []):
697
+ if test.get('meta', {}).get('mandatory', 0) == 1:
698
+ mandatory_tests.append(test.get('id'))
699
+ if mandatory_tests:
700
+ selected_tests = mandatory_tests
701
+ session['selected_tests'] = selected_tests
702
+
703
+ # Skip step 3 for GW only - redirect to step 4 if trying to access step 3
704
+ if step_num == 3 and flavor == FLAVOR_GW_ONLY:
705
+ if not selected_modules:
706
+ mandatory_modules = get_mandatory_modules_for_flavor(FLAVOR_GW_ONLY)
707
+ session['selected_modules'] = mandatory_modules
708
+ return redirect(url_for('step', step_num=4))
709
+
710
+ # Filter modules/tests based on flavor
711
+ filtered_modules = []
712
+ if flavor:
713
+ schema_path = session.get('schema_path')
714
+ schema_data = load_schema_data(schema_path) if schema_path else None
715
+ filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data)
716
+
717
+ # Get certification status
718
+ is_certified = True
719
+ missing_modules = []
720
+ missing_tests = []
721
+ missing_tests_details = [] # List of dicts with test id and label
722
+ missing_additional_module = False
723
+ if step_num >= 4 and flavor and selected_modules and selected_tests:
724
+ # Convert selected_modules to module dicts for check_certification_status
725
+ module_dicts = [m for m in filtered_modules if m.get('name') in selected_modules]
726
+ is_certified, missing_modules, missing_tests, missing_additional_module = check_certification_status(
727
+ flavor, module_dicts, selected_tests, tests_schema
728
+ )
729
+
730
+ # Get test details (labels) for missing tests
731
+ if missing_tests:
732
+ for mod in filtered_modules:
733
+ for test in mod.get('tests', []):
734
+ test_id = test.get('id')
735
+ if test_id in missing_tests:
736
+ missing_tests_details.append({
737
+ 'id': test_id,
738
+ 'label': test.get('label', test_id),
739
+ 'module': mod.get('name', '')
740
+ })
741
+
742
+ # Schema verification
743
+ schema_errors = []
744
+ schema_warnings = []
745
+ if step_num == 5 and schema_path and flavor:
746
+ schema_data = load_schema_data(schema_path)
747
+ if schema_data:
748
+ module_dicts = [m for m in filtered_modules if m.get('name') in selected_modules]
749
+ is_valid, errors, warnings = verify_schema_matches_selection(
750
+ schema_data, flavor, selected_modules, selected_tests
751
+ )
752
+ schema_errors = errors
753
+ schema_warnings = warnings
754
+
755
+ # Get CLI schemas for form fields - use certificate_cli for all flavors
756
+ cert_cli = CertificateCLI()
757
+ cert_schema = web_utils.parser_to_schema(cert_cli.parser)
758
+
759
+ # Calculate total test count including mandatory tests from selected modules
760
+ total_test_count = len(selected_tests)
761
+ if step_num >= 4 and flavor and selected_modules:
762
+ # Find all mandatory tests from selected modules that might not be in selected_tests
763
+ for mod in filtered_modules:
764
+ if mod.get('name') in selected_modules:
765
+ for test in mod.get('tests', []):
766
+ test_id = test.get('id')
767
+ if test.get('meta', {}).get('mandatory', 0) == 1 and test_id not in selected_tests:
768
+ total_test_count += 1
769
+
770
+ # Prepare data for JavaScript validation
771
+ # Always initialize these to avoid Undefined errors in template
772
+ mandatory_modules_for_js = get_mandatory_modules_for_flavor(flavor) if flavor else []
773
+ mandatory_tests_for_js = []
774
+
775
+ if step_num in [3, 4] and flavor:
776
+ if step_num == 4 and selected_modules:
777
+ # For step 4, only include mandatory tests from selected modules
778
+ for mod in filtered_modules:
779
+ if mod.get('name') in selected_modules:
780
+ for test in mod.get('tests', []):
781
+ if test.get('meta', {}).get('mandatory', 0) == 1:
782
+ mandatory_tests_for_js.append({
783
+ 'id': test.get('id'),
784
+ 'label': test.get('label', test.get('id')),
785
+ 'module': mod.get('name', '')
786
+ })
787
+ elif step_num == 3:
788
+ # For step 3, include all mandatory tests from all modules (for validation)
789
+ for mod in filtered_modules:
790
+ for test in mod.get('tests', []):
791
+ if test.get('meta', {}).get('mandatory', 0) == 1:
792
+ mandatory_tests_for_js.append({
793
+ 'id': test.get('id'),
794
+ 'label': test.get('label', test.get('id')),
795
+ 'module': mod.get('name', '')
796
+ })
797
+
798
+ # Get run completion info before clearing (for modal display)
799
+ run_completed = session.get('run_completed', False)
800
+ run_terminal = session.get('run_terminal')
801
+ run_pid = session.get('run_pid')
802
+ run_is_certified = session.get('run_is_certified')
803
+
804
+ # Clear run flags after getting them (so modal only shows once)
805
+ if run_completed:
806
+ session.pop('run_completed', None)
807
+ session.pop('run_terminal', None)
808
+ session.pop('run_pid', None)
809
+ session.pop('run_is_certified', None)
810
+
811
+ # Get available COM ports for port dropdown
812
+ available_ports = []
813
+ try:
814
+ for port, desc, hwid in sorted(serial.tools.list_ports.comports()):
815
+ available_ports.append({
816
+ 'value': port,
817
+ 'label': f"{port}: {desc} [{hwid}]"
818
+ })
819
+ except Exception:
820
+ # If serial tools are not available, leave empty list
821
+ pass
822
+
823
+ return render_template('cert_run.html',
824
+ app=web_utils.CERT_WEB,
825
+ title=title,
826
+ step_num=step_num,
827
+ flavor=flavor,
828
+ schema_path=schema_path,
829
+ selected_modules=selected_modules,
830
+ selected_tests=selected_tests,
831
+ total_test_count=total_test_count,
832
+ filtered_modules=filtered_modules,
833
+ tests_schema=tests_schema,
834
+ cert_schema=cert_schema,
835
+ is_certified=is_certified,
836
+ missing_modules=missing_modules,
837
+ missing_tests=missing_tests,
838
+ missing_tests_details=missing_tests_details,
839
+ missing_additional_module=missing_additional_module,
840
+ schema_errors=schema_errors,
841
+ schema_warnings=schema_warnings,
842
+ mandatory_modules_for_js=mandatory_modules_for_js,
843
+ mandatory_tests_for_js=mandatory_tests_for_js,
844
+ error=error,
845
+ warning=warning,
846
+ form_data=_prepare_form_data_for_template(session, cert_schema),
847
+ run_completed=run_completed,
848
+ run_terminal=run_terminal,
849
+ run_pid=run_pid,
850
+ run_is_certified=run_is_certified,
851
+ available_ports=available_ports)
852
+
853
+ def execute_certificate():
854
+ """Execute the certificate run based on session data."""
855
+ flavor = session.get('flavor')
856
+ schema_path = session.get('schema_path')
857
+ selected_tests = session.get('selected_tests', [])
858
+ form_data = session.get('form_data', {})
859
+
860
+ if not flavor or not schema_path or not selected_tests:
861
+ flash("Missing required information. Please start over.", "error")
862
+ return redirect(url_for('step', step_num=1))
863
+
864
+ # Strip any quotes that might have been added and normalize path
865
+ schema_path = str(schema_path).strip().strip('"').strip("'")
866
+ schema_path = os.path.normpath(schema_path)
867
+
868
+ # Verify schema file exists
869
+ if not os.path.exists(schema_path):
870
+ flash(f"Validation schema file not found: {schema_path}", "error")
871
+ return redirect(url_for('step', step_num=2))
872
+
873
+ # Check certification status
874
+ tests_schema = web_utils.scan_tests_dir(CERT_TESTS_ROOT)
875
+ filtered_modules = filter_modules_by_flavor(tests_schema, flavor)
876
+ module_dicts = [m for m in filtered_modules if m.get('name') in session.get('selected_modules', [])]
877
+ is_certified, _, _, _ = check_certification_status(flavor, module_dicts, selected_tests, tests_schema)
878
+
879
+ full_cmd = []
880
+
881
+ # Create a request-like object for form_to_argv
882
+ class FormDict:
883
+ def __init__(self, data):
884
+ self.data = data
885
+ def get(self, key, default=""):
886
+ return self.data.get(key, default)
887
+ def getlist(self, key):
888
+ val = self.data.get(key)
889
+ if isinstance(val, list):
890
+ return val
891
+ elif val:
892
+ return [val]
893
+ else:
894
+ return []
895
+
896
+ # Ensure custom_broker_path from session is in form_data if available
897
+ custom_broker_path = session.get('custom_broker_path')
898
+ if custom_broker_path and custom_broker_path not in form_data.values():
899
+ cert_cli = CertificateCLI()
900
+ cert_schema = web_utils.parser_to_schema(cert_cli.parser)
901
+ custom_broker_field_name = f"{cert_schema['title']}_custom_broker"
902
+ # Only add if not already present
903
+ if custom_broker_field_name not in form_data:
904
+ form_data[custom_broker_field_name] = custom_broker_path
905
+
906
+ form_dict = FormDict(form_data)
907
+
908
+ cert_cli = CertificateCLI()
909
+ cert_schema = web_utils.parser_to_schema(cert_cli.parser)
910
+
911
+ # Create test list
912
+ tl = _write_testlist(selected_tests, form_dict, is_certified)
913
+
914
+ argv = form_to_argv(form_dict, parser=cert_cli.parser, title=cert_schema['title'])
915
+ # Use absolute path and ensure it's properly formatted (already normalized above)
916
+ schema_path_abs = os.path.abspath(schema_path)
917
+ full_cmd = ["wlt-cert-cli", "--tl", tl, "--validation_schema", schema_path_abs] + argv
918
+ # Add --non_cert_run flag if not certified
919
+ if not is_certified:
920
+ full_cmd.append("--non_cert_run")
921
+
922
+ if full_cmd:
923
+ p = web_utils.open_in_terminal(full_cmd, preferred=PREFERRED_TERMINAL)
924
+ pid = getattr(p, "pid", None)
925
+
926
+ # Store run information for popup display
927
+ session['run_completed'] = True
928
+ session['run_terminal'] = PREFERRED_TERMINAL
929
+ session['run_pid'] = str(pid) if pid else None
930
+ session['run_is_certified'] = is_certified
931
+ return redirect(url_for('step', step_num=5))
932
+
933
+ @app.route("/export_config", methods=['GET'])
934
+ def export_config():
935
+ """Export current run configuration to a JSON file."""
936
+ if not session.get('flavor') or not session.get('schema_path'):
937
+ flash("No configuration to export. Please complete at least step 1 and 2.", "error")
938
+ return redirect(url_for('step', step_num=session.get('current_step', 1)))
939
+
940
+ # Prepare configuration data
941
+ config = {
942
+ 'version': '1.0',
943
+ 'exported_at': datetime.datetime.now().isoformat(),
944
+ 'flavor': session.get('flavor'),
945
+ 'schema_path': session.get('schema_path'),
946
+ 'selected_modules': session.get('selected_modules', []),
947
+ 'selected_tests': session.get('selected_tests', []),
948
+ 'form_data': session.get('form_data', {}),
949
+ 'schema_data': load_schema_data(session.get('schema_path')), # Load schema data from file for export
950
+ 'custom_broker_path': session.get('custom_broker_path') # Include custom broker file path
951
+ }
952
+
953
+ # Create filename with timestamp
954
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
955
+ flavor_name = session.get('flavor', 'unknown').replace('_', '-')
956
+ filename = f"certificate_run_{flavor_name}_{timestamp}.json"
957
+ filepath = os.path.join(CONFIG_FOLDER, filename)
958
+
959
+ # Save configuration
960
+ with open(filepath, 'w', encoding='utf-8') as f:
961
+ json.dump(config, f, indent=2, ensure_ascii=False)
962
+
963
+ # Send file for download
964
+ return send_file(filepath, as_attachment=True, download_name=filename, mimetype='application/json')
965
+
966
+ @app.route("/import_config", methods=['POST'])
967
+ def import_config():
968
+ """Import a previously saved run configuration."""
969
+ if 'config_file' not in request.files:
970
+ flash("No file selected", "error")
971
+ return redirect(url_for('step', step_num=1))
972
+
973
+ file = request.files['config_file']
974
+ if not file or not file.filename:
975
+ flash("No file selected", "error")
976
+ return redirect(url_for('step', step_num=1))
977
+
978
+ if not allowed_file(file.filename):
979
+ flash("Invalid file type. Please upload a JSON file.", "error")
980
+ return redirect(url_for('step', step_num=1))
981
+
982
+ try:
983
+ # Read and parse JSON
984
+ config_data = json.load(file)
985
+
986
+ # Validate configuration structure
987
+ if 'version' not in config_data or 'flavor' not in config_data:
988
+ flash("Invalid configuration file format", "error")
989
+ return redirect(url_for('step', step_num=1))
990
+
991
+ # Restore session data
992
+ session['flavor'] = config_data.get('flavor')
993
+ session['schema_path'] = config_data.get('schema_path')
994
+ session['selected_modules'] = config_data.get('selected_modules', [])
995
+ session['selected_tests'] = config_data.get('selected_tests', [])
996
+ session['form_data'] = config_data.get('form_data', {})
997
+ # Don't restore schema_data to session (load from file when needed to avoid cookie size issues)
998
+ # If schema_data is in config, save it back to the schema_path file if the path exists
999
+ schema_path = config_data.get('schema_path')
1000
+ schema_data = config_data.get('schema_data')
1001
+ if schema_path and schema_data and os.path.exists(schema_path):
1002
+ # Schema file exists, no need to restore schema_data to session
1003
+ pass
1004
+ elif schema_path and schema_data:
1005
+ # Schema file doesn't exist but we have the data - save it
1006
+ try:
1007
+ with open(schema_path, 'w', encoding='utf-8') as f:
1008
+ json.dump(schema_data, f, indent=2)
1009
+ except Exception:
1010
+ pass # If we can't save, user will need to re-upload
1011
+ session['custom_broker_path'] = config_data.get('custom_broker_path')
1012
+
1013
+ # Validate schema file still exists
1014
+ if session['schema_path'] and not os.path.exists(session['schema_path']):
1015
+ flash(f"Warning: Schema file not found at {session['schema_path']}. Please re-upload it in step 2.", "warning")
1016
+ session['current_step'] = 2
1017
+ # Validate custom_broker file still exists if present
1018
+ elif session.get('custom_broker_path') and not os.path.exists(session['custom_broker_path']):
1019
+ flash(f"Warning: Custom broker file not found at {session['custom_broker_path']}. Please re-upload it in step 4.", "warning")
1020
+ if session.get('current_step', 0) < 4:
1021
+ session['current_step'] = 4
1022
+ else:
1023
+ # Determine appropriate step based on what's configured
1024
+ if session.get('selected_tests'):
1025
+ session['current_step'] = 5
1026
+ elif session.get('selected_modules'):
1027
+ session['current_step'] = 4
1028
+ elif session.get('schema_path'):
1029
+ if session.get('flavor') == FLAVOR_GW_ONLY:
1030
+ session['current_step'] = 4
1031
+ else:
1032
+ session['current_step'] = 3
1033
+ else:
1034
+ session['current_step'] = 2
1035
+
1036
+ flash("Configuration loaded successfully!", "success")
1037
+ return redirect(url_for('step', step_num=session['current_step']))
1038
+
1039
+ except json.JSONDecodeError:
1040
+ flash("Invalid JSON file", "error")
1041
+ return redirect(url_for('step', step_num=1))
1042
+ except Exception as e:
1043
+ flash(f"Error loading configuration: {str(e)}", "error")
1044
+ return redirect(url_for('step', step_num=1))
1045
+
1046
+ @app.route("/clear")
1047
+ def clear_session():
1048
+ """Clear session and start fresh."""
1049
+ session.clear()
1050
+ return redirect(url_for('step', step_num=1))
1051
+
1052
+ @app.route("/parser")
1053
+ def parser():
1054
+ return web_utils.utils_parser(web_utils.CERT_WEB)
1055
+
1056
+ @app.route("/generator")
1057
+ def generator():
1058
+ return web_utils.utils_generator(web_utils.CERT_WEB)
1059
+
1060
+ @app.route("/data_structs", methods=['GET', 'POST'])
1061
+ def data_structs():
1062
+ return web_utils.utils_data_structs(web_utils.CERT_WEB)