wiliot-certificate 4.5.0__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 (125) hide show
  1. certificate/ag/wlt_types_ag_jsons/brg2brg_ota.json +211 -0
  2. certificate/ag/wlt_types_ag_jsons/brg2gw_hb.json +894 -0
  3. certificate/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +184 -0
  4. certificate/ag/wlt_types_ag_jsons/calibration.json +490 -0
  5. certificate/ag/wlt_types_ag_jsons/custom.json +614 -0
  6. certificate/ag/wlt_types_ag_jsons/datapath.json +900 -0
  7. certificate/ag/wlt_types_ag_jsons/energy2400.json +670 -0
  8. certificate/ag/wlt_types_ag_jsons/energySub1g.json +691 -0
  9. certificate/ag/wlt_types_ag_jsons/externalSensor.json +727 -0
  10. certificate/ag/wlt_types_ag_jsons/interface.json +1095 -0
  11. certificate/ag/wlt_types_ag_jsons/powerManagement.json +1439 -0
  12. certificate/ag/wlt_types_ag_jsons/side_info_sensor.json +105 -0
  13. certificate/ag/wlt_types_ag_jsons/signal_indicator_data.json +77 -0
  14. certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +126 -0
  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 +46 -75
  18. certificate/cert_config.py +18 -43
  19. certificate/cert_data_sim.py +9 -12
  20. certificate/cert_defines.py +0 -9
  21. certificate/cert_gw_sim.py +7 -35
  22. certificate/cert_mqtt.py +5 -15
  23. certificate/cert_prints.py +0 -1
  24. certificate/cert_results.py +37 -56
  25. certificate/cert_utils.py +15 -27
  26. certificate/certificate.py +5 -12
  27. certificate/certificate_cli.py +13 -10
  28. certificate/certificate_eth_test_list.txt +4 -6
  29. certificate/certificate_sanity_test_list.txt +2 -3
  30. certificate/certificate_test_list.txt +4 -5
  31. certificate/tests/calibration/interval_test/interval_test.json +0 -1
  32. certificate/tests/calibration/output_power_test/output_power_test.json +0 -1
  33. certificate/tests/calibration/pattern_test/pattern_test.json +0 -1
  34. certificate/tests/cloud_connectivity/acl_ext_adv_test/acl_ext_adv_test.json +1 -2
  35. certificate/tests/cloud_connectivity/acl_test/acl_test.json +1 -2
  36. certificate/tests/cloud_connectivity/acl_test/acl_test.py +15 -13
  37. certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.json +1 -2
  38. certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.py +6 -8
  39. certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.json +1 -2
  40. certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.py +3 -3
  41. certificate/tests/cloud_connectivity/connection_test/connection_test.json +0 -1
  42. certificate/tests/cloud_connectivity/connection_test/connection_test.py +13 -6
  43. certificate/tests/cloud_connectivity/downlink_test/downlink_test.json +0 -1
  44. certificate/tests/cloud_connectivity/downlink_test/downlink_test.py +4 -1
  45. certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.json +1 -2
  46. certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.py +14 -20
  47. certificate/tests/cloud_connectivity/reboot_test/reboot_test.json +0 -1
  48. certificate/tests/cloud_connectivity/reboot_test/reboot_test.py +0 -2
  49. certificate/tests/cloud_connectivity/registration_test/registration_test.json +0 -1
  50. certificate/tests/cloud_connectivity/registration_test/registration_test_cli.py +1 -1
  51. certificate/tests/cloud_connectivity/stress_test/stress_test.json +1 -2
  52. certificate/tests/cloud_connectivity/stress_test/stress_test.py +16 -20
  53. certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.json +0 -1
  54. certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.py +2 -1
  55. certificate/tests/cloud_connectivity/uplink_test/uplink_test.json +0 -1
  56. certificate/tests/cloud_connectivity/uplink_test/uplink_test.py +20 -28
  57. certificate/tests/datapath/aging_test/aging_test.json +0 -1
  58. certificate/tests/datapath/aging_test/aging_test.py +3 -7
  59. certificate/tests/datapath/event_ble5_test/event_ble5_test.json +2 -3
  60. certificate/tests/datapath/event_ble5_test/event_ble5_test.py +13 -7
  61. certificate/tests/datapath/event_test/event_test.json +2 -3
  62. certificate/tests/datapath/event_test/event_test.py +10 -5
  63. certificate/tests/datapath/num_of_tags_test/num_of_tags_test.json +2 -3
  64. certificate/tests/datapath/num_of_tags_test/num_of_tags_test.py +5 -9
  65. certificate/tests/datapath/output_power_test/output_power_test.json +0 -1
  66. certificate/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.json +0 -1
  67. certificate/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.py +4 -4
  68. certificate/tests/datapath/pacer_interval_test/pacer_interval_test.json +0 -1
  69. certificate/tests/datapath/pattern_test/pattern_test.json +0 -1
  70. certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.json +0 -1
  71. certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.py +5 -5
  72. certificate/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.json +0 -1
  73. certificate/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.py +5 -5
  74. certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.json +0 -1
  75. certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.py +8 -10
  76. certificate/tests/datapath/pkt_filter_gen3_test/pkt_filter_gen3_test.json +0 -1
  77. certificate/tests/datapath/pkt_filter_test/pkt_filter_test.json +0 -1
  78. certificate/tests/datapath/rssi_threshold_test/rssi_threshold_test.json +1 -2
  79. certificate/tests/datapath/rx_channel_hopping_test/rx_channel_hopping_test.json +0 -1
  80. certificate/tests/datapath/rx_channel_test/rx_channel_test.json +0 -1
  81. certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.json +0 -1
  82. certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +1 -1
  83. certificate/tests/datapath/rx_rate_gen3_test/rx_rate_gen3_test.json +0 -1
  84. certificate/tests/datapath/stress_gen3_test/stress_gen3_test.json +0 -1
  85. certificate/tests/datapath/stress_gen3_test/stress_gen3_test.py +0 -3
  86. certificate/tests/datapath/stress_test/stress_test.json +0 -1
  87. certificate/tests/datapath/stress_test/stress_test.py +0 -3
  88. certificate/tests/datapath/tx_repetition_test/tx_repetition_test.json +0 -1
  89. certificate/tests/edge_mgmt/action_blink_test/action_blink_test.json +0 -1
  90. certificate/tests/edge_mgmt/action_get_battery_sensor_test/action_get_battery_sensor_test.json +0 -1
  91. certificate/tests/edge_mgmt/action_get_module_test/action_get_module_test.json +0 -1
  92. certificate/tests/edge_mgmt/action_get_pof_data_test/action_get_pof_data_test.json +0 -1
  93. certificate/tests/edge_mgmt/action_gw_hb_test/action_gw_hb_test.json +0 -1
  94. certificate/tests/edge_mgmt/action_reboot_test/action_reboot_test.json +0 -1
  95. certificate/tests/edge_mgmt/action_restore_defaults_test/action_restore_defaults_test.json +0 -1
  96. certificate/tests/edge_mgmt/action_send_hb_test/action_send_hb_test.json +0 -1
  97. certificate/tests/edge_mgmt/action_send_hb_test/action_send_hb_test.py +14 -18
  98. certificate/tests/edge_mgmt/periodic_msgs_test/periodic_msgs_test.json +0 -1
  99. certificate/tests/energy2400/duty_cycle_test/duty_cycle_test.json +0 -1
  100. certificate/tests/energy2400/output_power_test/output_power_test.json +0 -1
  101. certificate/tests/energy2400/pattern_test/pattern_test.json +0 -1
  102. certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.json +0 -1
  103. certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +3 -4
  104. certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.json +9 -9
  105. certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.py +271 -113
  106. certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.json +0 -1
  107. certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +1 -1
  108. certificate/tests/energy_sub1g/duty_cycle_test/duty_cycle_test.json +0 -1
  109. certificate/tests/energy_sub1g/pattern_test/pattern_test.json +0 -1
  110. certificate/tests/pwr_mgmt/pwr_mgmt_test/pwr_mgmt_test.json +0 -1
  111. certificate/tests/sensors/ext_sensor_test/ext_sensor_test.json +0 -1
  112. certificate/tests/sensors/ext_sensor_test/ext_sensor_test.py +9 -4
  113. common/api_if/api_validation.py +2 -8
  114. common/web/templates/generator.html +79 -141
  115. common/web/web_utils.py +56 -78
  116. gui_certificate/server.py +78 -283
  117. gui_certificate/templates/cert_run.html +113 -179
  118. {wiliot_certificate-4.5.0.dist-info → wiliot_certificate-4.5.0a1.dist-info}/METADATA +38 -27
  119. {wiliot_certificate-4.5.0.dist-info → wiliot_certificate-4.5.0a1.dist-info}/RECORD +123 -109
  120. certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.json +0 -15
  121. certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.py +0 -80
  122. {wiliot_certificate-4.5.0.dist-info → wiliot_certificate-4.5.0a1.dist-info}/WHEEL +0 -0
  123. {wiliot_certificate-4.5.0.dist-info → wiliot_certificate-4.5.0a1.dist-info}/entry_points.txt +0 -0
  124. {wiliot_certificate-4.5.0.dist-info → wiliot_certificate-4.5.0a1.dist-info}/licenses/LICENSE +0 -0
  125. {wiliot_certificate-4.5.0.dist-info → wiliot_certificate-4.5.0a1.dist-info}/top_level.txt +0 -0
gui_certificate/server.py CHANGED
@@ -1,8 +1,7 @@
1
1
  import os
2
2
  import json
3
3
  import tempfile
4
- import datetime
5
- from flask import Flask, render_template, request, session, redirect, url_for, flash, send_file
4
+ from flask import Flask, render_template, request, session, redirect, url_for, flash, send_file, jsonify
6
5
  from jinja2 import ChoiceLoader, PackageLoader
7
6
  from werkzeug.utils import secure_filename
8
7
  from certificate.certificate_cli import CertificateCLI
@@ -32,23 +31,9 @@ TEMP_BASE = tempfile.gettempdir()
32
31
  UPLOAD_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_uploaded_schemas")
33
32
  CONFIG_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_saved_configs")
34
33
  TEST_LIST_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_test_lists")
35
- CUSTOM_BROKER_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_custom_brokers")
36
34
  ALLOWED_EXTENSIONS = {'json'}
37
35
  MAX_UPLOAD_SIZE = 16 * 1024 * 1024 # 16MB
38
36
 
39
- # Default custom broker configuration (from hivemq.json)
40
- DEFAULT_CUSTOM_BROKER = {
41
- "port": 8883,
42
- "brokerUrl": "mqtts://broker.hivemq.com",
43
- "username": "",
44
- "password": "",
45
- "ownerId": "wiliot"
46
- }
47
-
48
- CUSTOM_BROKER_HELP = {
49
- "ownerId": "Used to construct topics, e.g.: data-v2/<ownerId>/<gatewayId>"
50
- }
51
-
52
37
  app = Flask(__name__)
53
38
  app.secret_key = os.urandom(24) # Required for sessions
54
39
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@@ -58,7 +43,6 @@ app.config['MAX_CONTENT_LENGTH'] = MAX_UPLOAD_SIZE
58
43
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
59
44
  os.makedirs(CONFIG_FOLDER, exist_ok=True)
60
45
  os.makedirs(TEST_LIST_FOLDER, exist_ok=True)
61
- os.makedirs(CUSTOM_BROKER_FOLDER, exist_ok=True)
62
46
 
63
47
  # extend Jinja search path to include shared dir
64
48
  app.jinja_loader = ChoiceLoader([
@@ -86,32 +70,9 @@ def get_mandatory_modules_for_flavor(flavor):
86
70
  return [MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT]
87
71
  return []
88
72
 
89
- def get_mandatory_modules_by_schema(schema_data, flavor):
90
- """Get modules that are mandatory by schema (modules declared in schema)."""
91
- if not schema_data or "modules" not in schema_data:
92
- return []
93
-
94
- # Only apply to bridge flavors (bridge_only and combo)
95
- if flavor not in (FLAVOR_BRIDGE_ONLY, FLAVOR_COMBO):
96
- return []
97
-
98
- schema_modules = schema_data.get("modules", {})
99
- if not isinstance(schema_modules, dict):
100
- return []
101
-
102
- # Map schema module names to test module names
103
- mandatory_by_schema = []
104
- for schema_mod_name in schema_modules.keys():
105
- # Map schema module name to test module name
106
- test_mod_name = test_module_and_schema_module_to_other(schema_mod_name)
107
- if test_mod_name not in mandatory_by_schema and test_mod_name != 'custom':
108
- mandatory_by_schema.append(test_mod_name)
109
-
110
- return mandatory_by_schema
111
-
112
73
  # TODO: This is a temporary mapping to map test module names to schema module names - remove this
113
- def test_module_and_schema_module_to_other(module_name):
114
- """Map test module names and schema module names to one another."""
74
+ def test_module_to_schema_module(test_module_name):
75
+ """Map test module names to validation schema module names."""
115
76
  mapping = {
116
77
  "calibration": "calibration",
117
78
  "datapath": "datapath",
@@ -121,12 +82,7 @@ def test_module_and_schema_module_to_other(module_name):
121
82
  "sensors": "externalSensor", # different name
122
83
  "custom": "custom"
123
84
  }
124
- for k, v in mapping.items():
125
- if module_name == k:
126
- return v
127
- if module_name == v:
128
- return k
129
- return module_name # Return as-is if not in mapping
85
+ return mapping.get(test_module_name, test_module_name) # Return as-is if not in mapping
130
86
 
131
87
  def filter_modules_by_flavor(tests_schema, flavor, schema_data=None):
132
88
  """Filter modules and tests based on selected flavor."""
@@ -159,17 +115,12 @@ def filter_modules_by_flavor(tests_schema, flavor, schema_data=None):
159
115
  # Include all tests for combo
160
116
  filtered_tests.append(test)
161
117
 
162
- # Get modules mandatory by schema
163
- mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
164
- is_mandatory_by_schema = mod_name in mandatory_by_schema
165
-
166
- # Only include module if it has tests or is mandatory (by flavor or schema)
118
+ # Only include module if it has tests or is mandatory
167
119
  # For mandatory modules, include even if no tests match (they'll be shown but empty)
168
- if filtered_tests or mod_name in mandatory_modules or is_mandatory_by_schema:
120
+ if filtered_tests or mod_name in mandatory_modules:
169
121
  mod_copy = mod.copy()
170
122
  mod_copy["tests"] = filtered_tests
171
123
  mod_copy["is_mandatory"] = mod_name in mandatory_modules
172
- mod_copy["is_mandatory_by_schema"] = is_mandatory_by_schema
173
124
  filtered_modules.append(mod_copy)
174
125
 
175
126
  # Sort modules: cloud_connectivity first, then edge_mgmt, then by schema order
@@ -185,7 +136,7 @@ def filter_modules_by_flavor(tests_schema, flavor, schema_data=None):
185
136
  if schema_module_order:
186
137
  try:
187
138
  # Map test module name to schema module name
188
- schema_mod_name = test_module_and_schema_module_to_other(mod_name)
139
+ schema_mod_name = test_module_to_schema_module(mod_name)
189
140
  idx = schema_module_order.index(schema_mod_name)
190
141
  return (2, idx)
191
142
  except ValueError:
@@ -334,37 +285,30 @@ def verify_schema_matches_selection(schema_data, flavor, selected_modules, selec
334
285
  bridge_modules_to_check = [m for m in selected_modules if m not in (MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT)]
335
286
  for test_mod_name in bridge_modules_to_check:
336
287
  # Map test module name to schema module name
337
- schema_mod_name = test_module_and_schema_module_to_other(test_mod_name)
288
+ schema_mod_name = test_module_to_schema_module(test_mod_name)
338
289
  if schema_mod_name not in schema_module_names:
339
290
  warnings.append(f"Module '{test_mod_name}' may not be supported according to the uploaded schema")
340
291
 
341
292
  return len(errors) == 0, errors, warnings
342
293
 
343
- def check_certification_status(flavor, selected_modules, selected_tests, tests_schema, schema_data=None, unsterile_run=False):
294
+ def check_certification_status(flavor, selected_modules, selected_tests, tests_schema):
344
295
  """Check if the selection qualifies for certification or is test-only."""
345
296
  mandatory_modules = get_mandatory_modules_for_flavor(flavor)
346
- mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
347
297
  all_mandatory_tests = []
348
298
 
349
- # Get selected module names (handle both dict and string formats)
350
- selected_module_names = [m.get("name") if isinstance(m, dict) else m for m in selected_modules]
351
-
352
- # Get all mandatory tests from ALL selected modules (not just mandatory modules)
353
- # When a module is selected, its mandatory tests become mandatory
299
+ # Get all mandatory tests from mandatory modules
354
300
  for mod in tests_schema.get("modules", []):
355
301
  mod_name = mod.get("name", "")
356
- if mod_name in selected_module_names:
302
+ if mod_name in mandatory_modules:
357
303
  for test in mod.get("tests", []):
358
304
  if test.get("meta", {}).get("mandatory", 0) == 1:
359
305
  all_mandatory_tests.append(test.get("id"))
360
306
 
361
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]
362
309
  missing_modules = [m for m in mandatory_modules if m not in selected_module_names]
363
310
 
364
- # Check if all schema-mandatory modules are selected
365
- missing_modules_by_schema = [m for m in mandatory_by_schema if m not in selected_module_names]
366
-
367
- # Check if all mandatory tests (from selected modules) are selected
311
+ # Check if all mandatory tests are selected
368
312
  missing_tests = [t for t in all_mandatory_tests if t not in selected_tests]
369
313
 
370
314
  # Check for "at least one additional module" requirement for Bridge Only and Combo
@@ -375,11 +319,9 @@ def check_certification_status(flavor, selected_modules, selected_tests, tests_s
375
319
  if len(additional_modules) == 0:
376
320
  missing_additional_module = True
377
321
 
378
- # A certifying run must be sterile - if unsterile_run is set, force non-certifying
379
- is_certified = (len(missing_modules) == 0 and len(missing_modules_by_schema) == 0 and
380
- len(missing_tests) == 0 and not missing_additional_module and not unsterile_run)
322
+ is_certified = len(missing_modules) == 0 and len(missing_tests) == 0 and not missing_additional_module
381
323
 
382
- return is_certified, missing_modules, missing_tests, missing_additional_module, missing_modules_by_schema
324
+ return is_certified, missing_modules, missing_tests, missing_additional_module
383
325
 
384
326
  def _prepare_form_data_for_template(session, cert_schema):
385
327
  """Prepare form_data for template, ensuring custom_broker_path is included if available."""
@@ -446,18 +388,15 @@ def _sort_tests_by_module_priority(test_ids: list[str]) -> list[str]:
446
388
  Test ID format: 'module/test_name'
447
389
  """
448
390
  def test_sort_key(test_id: str):
449
- module, _, test_name = test_id.partition('/')
450
- # Connection test first (requires manual unplug and replug)
451
- if module == MODULE_CLOUD_CONNECTIVITY and "connection" in test_name.lower():
452
- return (0, test_id)
453
- # other cloud_connectivity tests
391
+ module = test_id.split('/')[0] if '/' in test_id else test_id
392
+ # cloud_connectivity first
454
393
  if module == MODULE_CLOUD_CONNECTIVITY:
455
- return (1, test_id)
394
+ return (0, test_id)
456
395
  # edge_mgmt second
457
396
  if module == MODULE_EDGE_MGMT:
458
- return (2, test_id)
397
+ return (1, test_id)
459
398
  # Others last
460
- return (3, test_id)
399
+ return (2, test_id)
461
400
 
462
401
  return sorted(test_ids, key=test_sort_key)
463
402
 
@@ -477,65 +416,14 @@ def _write_testlist(selected_ids: list[str], form_data=None, is_certified=False)
477
416
  f.write("# NON-CERTIFYING RUN (mandatory tests/modules missing)\n")
478
417
  for test_id in sorted_test_ids:
479
418
  tid_safe = test_id.replace('/', '_')
480
- # Check for dynamic parameter first (non-array format)
481
- dynamic_param_key = f"params_{tid_safe}_dynamic"
482
- dynamic_param = form_data.get(dynamic_param_key) if form_data else None
483
-
484
- if dynamic_param is not None and dynamic_param != "":
485
- # Use dynamic parameter value
486
- params = [str(dynamic_param)]
487
- else:
488
- # Use regular parameter array
489
- param_key = f"params_{tid_safe}[]"
490
- params = form_data.getlist(param_key) if form_data else [] # list[str]
419
+ param_key = f"params_{tid_safe}[]"
420
+ params = form_data.getlist(param_key) if form_data else [] # list[str]
491
421
  # Example line format: "<module/test> <param1> <param2> ..."
492
422
  line = " ".join([test_id] + params)
493
423
  f.write(line + "\n")
494
424
  print(f"Custom test list saved in {path}")
495
425
  return path
496
426
 
497
- def _write_custom_broker_config(form_data, cert_schema_title="cert_run") -> str:
498
- """
499
- Generate a custom broker JSON configuration file from form data.
500
- Extracts broker field values and creates a JSON file matching hivemq.json format.
501
- """
502
- # Extract broker configuration from form_data
503
- broker_config = {}
504
-
505
- # Field names follow pattern: {schema_title}_custom_broker_{field_name}
506
- field_prefix = f"{cert_schema_title}_custom_broker_"
507
-
508
- # Extract values from form_data, using defaults if not provided
509
- for field in DEFAULT_CUSTOM_BROKER.keys():
510
- field_name = f"{field_prefix}{field}"
511
- value = form_data.get(field_name, "")
512
-
513
- if field == "port":
514
- # Convert port to int if it's a number
515
- try:
516
- broker_config[field] = int(value) if value else DEFAULT_CUSTOM_BROKER[field]
517
- except (ValueError, TypeError):
518
- broker_config[field] = DEFAULT_CUSTOM_BROKER[field]
519
- elif field == "ownerId":
520
- # Convert ownerId to topics
521
- broker_config[CUSTOM_BROKER_UPDATE_TOPIC] = f'update/{value}/<{GW_ID}>'
522
- broker_config[CUSTOM_BROKER_DATA_TOPIC] = f'data/{value}/<{GW_ID}>'
523
- broker_config[CUSTOM_BROKER_STATUS_TOPIC] = f'status/{value}/<{GW_ID}>'
524
- else:
525
- broker_config[field] = value
526
-
527
- # Generate filename with timestamp
528
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
529
- filename = f"custom_broker_{timestamp}.json"
530
- filepath = os.path.join(CUSTOM_BROKER_FOLDER, filename)
531
-
532
- # Write JSON file
533
- with open(filepath, 'w', encoding='utf-8') as f:
534
- json.dump(broker_config, f, indent=4)
535
-
536
- print(f"Custom broker config saved in {filepath}")
537
- return filepath
538
-
539
427
  @app.route("/")
540
428
  def index():
541
429
  """Redirect to step 1 or show current step."""
@@ -563,7 +451,6 @@ def step(step_num):
563
451
  session['selected_modules'] = []
564
452
  session['selected_tests'] = []
565
453
  session['form_data'] = {}
566
- session['schema_mandatory_initialized'] = False
567
454
 
568
455
  title = "Run Certificate"
569
456
  tests_schema = web_utils.scan_tests_dir(CERT_TESTS_ROOT)
@@ -618,22 +505,7 @@ def step(step_num):
618
505
  flavor = session.get('flavor')
619
506
  if 'selected_modules' not in session or not session.get('selected_modules'):
620
507
  mandatory_modules = get_mandatory_modules_for_flavor(flavor)
621
- # Add schema-mandatory modules
622
- schema_data = result # result is the validated schema data
623
- mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
624
- all_mandatory = list(set(mandatory_modules + mandatory_by_schema))
625
- session['selected_modules'] = all_mandatory
626
- # Mark schema-mandatory modules as initialized
627
- session['schema_mandatory_initialized'] = True
628
- else:
629
- # If modules already exist, add schema-mandatory modules and mark as initialized
630
- schema_data = result
631
- mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
632
- current_modules = session.get('selected_modules', [])
633
- updated_modules = list(set(current_modules + mandatory_by_schema))
634
- if len(updated_modules) > len(current_modules):
635
- session['selected_modules'] = updated_modules
636
- session['schema_mandatory_initialized'] = True
508
+ session['selected_modules'] = mandatory_modules
637
509
  # For GW only, skip step 3 (modules)
638
510
  if session.get('flavor') == FLAVOR_GW_ONLY:
639
511
  session['current_step'] = 4
@@ -703,19 +575,43 @@ def step(step_num):
703
575
  else:
704
576
  form_data_dict[key] = request.form.get(key)
705
577
 
706
- # Handle custom_broker configuration from form fields
578
+ # Handle custom_broker file upload
707
579
  cert_cli = CertificateCLI()
708
580
  cert_schema = web_utils.parser_to_schema(cert_cli.parser)
709
581
  custom_broker_field_name = f"{cert_schema['title']}_custom_broker"
582
+ custom_broker_file_key = f"{custom_broker_field_name}_file"
710
583
 
711
- # Generate custom broker JSON file from form fields
712
- try:
713
- broker_config_path = _write_custom_broker_config(form_data_dict, cert_schema['title'])
714
- normalized_path = os.path.normpath(broker_config_path)
715
- session['custom_broker_path'] = normalized_path
716
- form_data_dict[custom_broker_field_name] = normalized_path
717
- except Exception as e:
718
- error = f"Error generating custom broker configuration: {e}"
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"
719
615
 
720
616
  # Validate all required fields (required for moving forward, but allow going back)
721
617
  if action == 'next':
@@ -735,22 +631,13 @@ def step(step_num):
735
631
  field_name = f"{cert_schema['title']}_{field['name']}"
736
632
  field_value = form_data_dict.get(field_name, '')
737
633
 
738
- # Special handling for custom_broker - check if all required broker fields are filled
634
+ # Special handling for custom_broker file upload
739
635
  if field['name'] == 'custom_broker':
740
- # Check all broker fields except username and password are filled
741
- broker_field_prefix = f"{cert_schema['title']}_custom_broker_"
742
- optional_fields = {'username', 'password'}
743
- for broker_field_key in DEFAULT_CUSTOM_BROKER.keys():
744
- if broker_field_key not in optional_fields:
745
- broker_field_name = f"{broker_field_prefix}{broker_field_key}"
746
- broker_field_value = form_data_dict.get(broker_field_name, '')
747
- if not broker_field_value or (isinstance(broker_field_value, str) and not broker_field_value.strip()):
748
- missing_required.append(f"custom_broker.{broker_field_key}")
749
- break
750
- # Also check if path was generated (should be generated above if fields are valid)
751
- if not field_value and not session.get('custom_broker_path'):
752
- # Only add if we haven't already added a broker field error
753
- if not any('custom_broker.' in req for req in missing_required):
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'):
754
641
  missing_required.append(field.get('name', field['label']))
755
642
  else:
756
643
  # For other fields, check if value is provided
@@ -794,30 +681,9 @@ def step(step_num):
794
681
  # This ensures mandatory items are only pre-checked at the beginning
795
682
  if flavor and not selected_modules:
796
683
  mandatory_modules = get_mandatory_modules_for_flavor(flavor)
797
- # Add schema-mandatory modules if schema is available
798
- schema_data = load_schema_data(schema_path) if schema_path else None
799
- mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
800
- all_mandatory = list(set(mandatory_modules + mandatory_by_schema))
801
- selected_modules = all_mandatory
684
+ selected_modules = mandatory_modules
802
685
  session['selected_modules'] = selected_modules
803
686
 
804
- # Ensure schema-mandatory modules are added when first reaching step 3 (only if not already initialized)
805
- # Use a session flag to track if schema-mandatory modules have been initialized
806
- if step_num == 3 and flavor and schema_path:
807
- schema_mandatory_initialized = session.get('schema_mandatory_initialized', False)
808
- if not schema_mandatory_initialized:
809
- schema_data = load_schema_data(schema_path) if schema_path else None
810
- if schema_data:
811
- mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
812
- # Add schema-mandatory modules that aren't already selected
813
- current_modules = session.get('selected_modules', [])
814
- updated_modules = list(set(current_modules + mandatory_by_schema))
815
- if len(updated_modules) > len(current_modules):
816
- selected_modules = updated_modules
817
- session['selected_modules'] = selected_modules
818
- # Mark as initialized so we don't re-add them when going back
819
- session['schema_mandatory_initialized'] = True
820
-
821
687
  # Initialize mandatory tests only if not already in session and we have selected modules
822
688
  # Only initialize when first reaching step 4 (not on every GET request)
823
689
  if flavor and selected_modules and not selected_tests and step_num == 4:
@@ -848,28 +714,17 @@ def step(step_num):
848
714
  schema_data = load_schema_data(schema_path) if schema_path else None
849
715
  filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data)
850
716
 
851
- # Get CLI schemas for form fields - use certificate_cli for all flavors (needed early for unsterile_run check)
852
- cert_cli = CertificateCLI()
853
- cert_schema = web_utils.parser_to_schema(cert_cli.parser)
854
-
855
- # Check if unsterile_run is set in form_data
856
- form_data = _prepare_form_data_for_template(session, cert_schema)
857
- unsterile_run_field = f"{cert_schema['title']}_unsterile_run"
858
- unsterile_run = bool(form_data.get(unsterile_run_field))
859
-
860
717
  # Get certification status
861
718
  is_certified = True
862
719
  missing_modules = []
863
720
  missing_tests = []
864
721
  missing_tests_details = [] # List of dicts with test id and label
865
722
  missing_additional_module = False
866
- missing_modules_by_schema = []
867
723
  if step_num >= 4 and flavor and selected_modules and selected_tests:
868
724
  # Convert selected_modules to module dicts for check_certification_status
869
725
  module_dicts = [m for m in filtered_modules if m.get('name') in selected_modules]
870
- schema_data = load_schema_data(schema_path) if schema_path else None
871
- is_certified, missing_modules, missing_tests, missing_additional_module, missing_modules_by_schema = check_certification_status(
872
- flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run
726
+ is_certified, missing_modules, missing_tests, missing_additional_module = check_certification_status(
727
+ flavor, module_dicts, selected_tests, tests_schema
873
728
  )
874
729
 
875
730
  # Get test details (labels) for missing tests
@@ -896,12 +751,10 @@ def step(step_num):
896
751
  )
897
752
  schema_errors = errors
898
753
  schema_warnings = warnings
899
- # Get missing schema-mandatory modules for display (recalculate if not already set)
900
- if not missing_modules_by_schema and step_num >= 4 and flavor and selected_modules:
901
- _, _, _, _, missing_modules_by_schema = check_certification_status(
902
- flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run
903
- )
904
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)
905
758
 
906
759
  # Calculate total test count including mandatory tests from selected modules
907
760
  total_test_count = len(selected_tests)
@@ -917,8 +770,6 @@ def step(step_num):
917
770
  # Prepare data for JavaScript validation
918
771
  # Always initialize these to avoid Undefined errors in template
919
772
  mandatory_modules_for_js = get_mandatory_modules_for_flavor(flavor) if flavor else []
920
- schema_data_for_js = load_schema_data(schema_path) if schema_path else None
921
- mandatory_modules_by_schema_for_js = get_mandatory_modules_by_schema(schema_data_for_js, flavor) if (schema_data_for_js and flavor) else []
922
773
  mandatory_tests_for_js = []
923
774
 
924
775
  if step_num in [3, 4] and flavor:
@@ -988,22 +839,16 @@ def step(step_num):
988
839
  missing_additional_module=missing_additional_module,
989
840
  schema_errors=schema_errors,
990
841
  schema_warnings=schema_warnings,
991
- missing_modules_by_schema=missing_modules_by_schema,
992
842
  mandatory_modules_for_js=mandatory_modules_for_js,
993
- mandatory_modules_by_schema_for_js=mandatory_modules_by_schema_for_js,
994
843
  mandatory_tests_for_js=mandatory_tests_for_js,
995
844
  error=error,
996
845
  warning=warning,
997
846
  form_data=_prepare_form_data_for_template(session, cert_schema),
998
- custom_broker_defaults=DEFAULT_CUSTOM_BROKER,
999
- custom_broker_help=CUSTOM_BROKER_HELP,
1000
- custom_broker_field_keys=list(DEFAULT_CUSTOM_BROKER.keys()),
1001
847
  run_completed=run_completed,
1002
848
  run_terminal=run_terminal,
1003
849
  run_pid=run_pid,
1004
850
  run_is_certified=run_is_certified,
1005
- available_ports=available_ports,
1006
- unsterile_run=unsterile_run)
851
+ available_ports=available_ports)
1007
852
 
1008
853
  def execute_certificate():
1009
854
  """Execute the certificate run based on session data."""
@@ -1029,13 +874,7 @@ def execute_certificate():
1029
874
  tests_schema = web_utils.scan_tests_dir(CERT_TESTS_ROOT)
1030
875
  filtered_modules = filter_modules_by_flavor(tests_schema, flavor)
1031
876
  module_dicts = [m for m in filtered_modules if m.get('name') in session.get('selected_modules', [])]
1032
- schema_data = load_schema_data(schema_path) if schema_path else None
1033
- # Check if unsterile_run is set in form_data
1034
- cert_cli = CertificateCLI()
1035
- cert_schema = web_utils.parser_to_schema(cert_cli.parser)
1036
- unsterile_run_field = f"{cert_schema['title']}_unsterile_run"
1037
- unsterile_run = bool(form_data.get(unsterile_run_field))
1038
- is_certified, _, _, _, _ = check_certification_status(flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run)
877
+ is_certified, _, _, _ = check_certification_status(flavor, module_dicts, selected_tests, tests_schema)
1039
878
 
1040
879
  full_cmd = []
1041
880
 
@@ -1098,17 +937,6 @@ def export_config():
1098
937
  flash("No configuration to export. Please complete at least step 1 and 2.", "error")
1099
938
  return redirect(url_for('step', step_num=session.get('current_step', 1)))
1100
939
 
1101
- # Extract custom broker form field values from form_data
1102
- form_data = session.get('form_data', {})
1103
- cert_cli = CertificateCLI()
1104
- cert_schema = web_utils.parser_to_schema(cert_cli.parser)
1105
- custom_broker_fields = {}
1106
- field_prefix = f"{cert_schema['title']}_custom_broker_"
1107
- for field_name in DEFAULT_CUSTOM_BROKER.keys():
1108
- full_field_name = f"{field_prefix}{field_name}"
1109
- if full_field_name in form_data:
1110
- custom_broker_fields[field_name] = form_data[full_field_name]
1111
-
1112
940
  # Prepare configuration data
1113
941
  config = {
1114
942
  'version': '1.0',
@@ -1117,10 +945,9 @@ def export_config():
1117
945
  'schema_path': session.get('schema_path'),
1118
946
  'selected_modules': session.get('selected_modules', []),
1119
947
  'selected_tests': session.get('selected_tests', []),
1120
- 'form_data': form_data,
948
+ 'form_data': session.get('form_data', {}),
1121
949
  'schema_data': load_schema_data(session.get('schema_path')), # Load schema data from file for export
1122
- 'custom_broker_path': session.get('custom_broker_path'), # Include custom broker file path for backward compatibility
1123
- 'custom_broker_fields': custom_broker_fields # Include custom broker form field values
950
+ 'custom_broker_path': session.get('custom_broker_path') # Include custom broker file path
1124
951
  }
1125
952
 
1126
953
  # Create filename with timestamp
@@ -1181,47 +1008,17 @@ def import_config():
1181
1008
  json.dump(schema_data, f, indent=2)
1182
1009
  except Exception:
1183
1010
  pass # If we can't save, user will need to re-upload
1184
- # Handle custom broker configuration
1185
- custom_broker_fields = config_data.get('custom_broker_fields', {})
1186
- if custom_broker_fields:
1187
- # Restore broker form field values to form_data
1188
- cert_cli = CertificateCLI()
1189
- cert_schema = web_utils.parser_to_schema(cert_cli.parser)
1190
- field_prefix = f"{cert_schema['title']}_custom_broker_"
1191
- for field_name, field_value in custom_broker_fields.items():
1192
- full_field_name = f"{field_prefix}{field_name}"
1193
- session['form_data'][full_field_name] = field_value
1194
- # Regenerate broker config file from form fields
1195
- try:
1196
- broker_config_path = _write_custom_broker_config(session['form_data'], cert_schema['title'])
1197
- session['custom_broker_path'] = os.path.normpath(broker_config_path)
1198
- except Exception:
1199
- pass # If generation fails, user can reconfigure in step 4
1200
- else:
1201
- # Backward compatibility: try to use existing path
1202
- session['custom_broker_path'] = config_data.get('custom_broker_path')
1011
+ session['custom_broker_path'] = config_data.get('custom_broker_path')
1203
1012
 
1204
1013
  # Validate schema file still exists
1205
1014
  if session['schema_path'] and not os.path.exists(session['schema_path']):
1206
1015
  flash(f"Warning: Schema file not found at {session['schema_path']}. Please re-upload it in step 2.", "warning")
1207
1016
  session['current_step'] = 2
1208
- # Validate custom_broker file still exists if present (or regenerate if we have fields)
1017
+ # Validate custom_broker file still exists if present
1209
1018
  elif session.get('custom_broker_path') and not os.path.exists(session['custom_broker_path']):
1210
- if custom_broker_fields:
1211
- # Regenerate from fields if file doesn't exist
1212
- try:
1213
- cert_cli = CertificateCLI()
1214
- cert_schema = web_utils.parser_to_schema(cert_cli.parser)
1215
- broker_config_path = _write_custom_broker_config(session['form_data'], cert_schema['title'])
1216
- session['custom_broker_path'] = os.path.normpath(broker_config_path)
1217
- except Exception:
1218
- flash(f"Warning: Custom broker configuration will be regenerated in step 4.", "warning")
1219
- if session.get('current_step', 0) < 4:
1220
- session['current_step'] = 4
1221
- else:
1222
- flash(f"Warning: Custom broker file not found at {session['custom_broker_path']}. Please reconfigure it in step 4.", "warning")
1223
- if session.get('current_step', 0) < 4:
1224
- session['current_step'] = 4
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
1225
1022
  else:
1226
1023
  # Determine appropriate step based on what's configured
1227
1024
  if session.get('selected_tests'):
@@ -1250,8 +1047,6 @@ def import_config():
1250
1047
  def clear_session():
1251
1048
  """Clear session and start fresh."""
1252
1049
  session.clear()
1253
- # Initialize session flags
1254
- session['schema_mandatory_initialized'] = False
1255
1050
  return redirect(url_for('step', step_num=1))
1256
1051
 
1257
1052
  @app.route("/parser")
@@ -1264,4 +1059,4 @@ def generator():
1264
1059
 
1265
1060
  @app.route("/data_structs", methods=['GET', 'POST'])
1266
1061
  def data_structs():
1267
- return web_utils.utils_data_structs(web_utils.CERT_WEB)
1062
+ return web_utils.utils_data_structs(web_utils.CERT_WEB)