wiliot-certificate 4.5.0a2__py3-none-any.whl → 4.5.0a4__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.
- certificate/cert_common.py +35 -18
- certificate/cert_config.py +6 -6
- certificate/cert_data_sim.py +12 -9
- certificate/cert_defines.py +6 -0
- certificate/cert_gw_sim.py +3 -3
- certificate/cert_mqtt.py +5 -4
- certificate/cert_results.py +42 -32
- certificate/cert_utils.py +9 -10
- certificate/certificate.py +7 -5
- certificate/certificate_cli.py +9 -12
- certificate/certificate_eth_test_list.txt +3 -2
- certificate/certificate_sanity_test_list.txt +3 -2
- certificate/certificate_test_list.txt +3 -3
- certificate/tests/cloud_connectivity/acl_test/acl_test.py +13 -15
- certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.json +1 -1
- certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.py +2 -2
- certificate/tests/cloud_connectivity/connection_test/connection_test.py +4 -13
- certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.py +1 -2
- certificate/tests/cloud_connectivity/downlink_test/downlink_test.py +1 -4
- certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.py +12 -6
- certificate/tests/cloud_connectivity/registration_test/registration_test_cli.py +1 -1
- certificate/tests/cloud_connectivity/stress_test/stress_test.py +12 -7
- certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.py +1 -2
- certificate/tests/cloud_connectivity/uplink_test/uplink_test.py +26 -20
- certificate/tests/datapath/event_ble5_test/event_ble5_test.json +1 -1
- certificate/tests/datapath/event_ble5_test/event_ble5_test.py +7 -13
- certificate/tests/datapath/event_test/event_test.json +1 -1
- certificate/tests/datapath/event_test/event_test.py +5 -10
- certificate/tests/datapath/pacer_interval_ble5_test/pacer_interval_ble5_test.py +4 -4
- certificate/tests/datapath/pkt_filter_ble5_chl21_test/pkt_filter_ble5_chl21_test.py +5 -5
- certificate/tests/datapath/pkt_filter_ble5_test/pkt_filter_ble5_test.py +5 -5
- certificate/tests/datapath/pkt_filter_brg2gw_ext_adv_test/pkt_filter_brg2gw_ext_adv_test.py +10 -8
- certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +1 -1
- certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +4 -3
- certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.json +8 -9
- certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.py +113 -271
- certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +1 -1
- certificate/tests/sensors/ext_sensor_test/ext_sensor_test.py +4 -9
- common/api_if/api_validation.py +6 -0
- common/web/templates/generator.html +141 -79
- common/web/web_utils.py +78 -56
- gui_certificate/server.py +255 -70
- gui_certificate/templates/cert_run.html +128 -98
- {wiliot_certificate-4.5.0a2.dist-info → wiliot_certificate-4.5.0a4.dist-info}/METADATA +6 -11
- {wiliot_certificate-4.5.0a2.dist-info → wiliot_certificate-4.5.0a4.dist-info}/RECORD +49 -65
- certificate/ag/wlt_types_ag_jsons/brg2brg_ota.json +0 -211
- certificate/ag/wlt_types_ag_jsons/brg2gw_hb.json +0 -894
- certificate/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +0 -184
- certificate/ag/wlt_types_ag_jsons/calibration.json +0 -490
- certificate/ag/wlt_types_ag_jsons/custom.json +0 -614
- certificate/ag/wlt_types_ag_jsons/datapath.json +0 -900
- certificate/ag/wlt_types_ag_jsons/energy2400.json +0 -670
- certificate/ag/wlt_types_ag_jsons/energySub1g.json +0 -691
- certificate/ag/wlt_types_ag_jsons/externalSensor.json +0 -727
- certificate/ag/wlt_types_ag_jsons/interface.json +0 -1095
- certificate/ag/wlt_types_ag_jsons/powerManagement.json +0 -1439
- certificate/ag/wlt_types_ag_jsons/side_info_sensor.json +0 -105
- certificate/ag/wlt_types_ag_jsons/signal_indicator_data.json +0 -77
- certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +0 -126
- certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json +0 -175
- certificate/ag/wlt_types_ag_jsons/unified_sensor_pkt.json +0 -65
- {wiliot_certificate-4.5.0a2.dist-info → wiliot_certificate-4.5.0a4.dist-info}/WHEEL +0 -0
- {wiliot_certificate-4.5.0a2.dist-info → wiliot_certificate-4.5.0a4.dist-info}/entry_points.txt +0 -0
- {wiliot_certificate-4.5.0a2.dist-info → wiliot_certificate-4.5.0a4.dist-info}/licenses/LICENSE +0 -0
- {wiliot_certificate-4.5.0a2.dist-info → wiliot_certificate-4.5.0a4.dist-info}/top_level.txt +0 -0
gui_certificate/server.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
3
|
import tempfile
|
|
4
|
-
|
|
4
|
+
import datetime
|
|
5
|
+
from flask import Flask, render_template, request, session, redirect, url_for, flash, send_file
|
|
5
6
|
from jinja2 import ChoiceLoader, PackageLoader
|
|
6
7
|
from werkzeug.utils import secure_filename
|
|
7
8
|
from certificate.certificate_cli import CertificateCLI
|
|
@@ -31,9 +32,21 @@ TEMP_BASE = tempfile.gettempdir()
|
|
|
31
32
|
UPLOAD_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_uploaded_schemas")
|
|
32
33
|
CONFIG_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_saved_configs")
|
|
33
34
|
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")
|
|
34
36
|
ALLOWED_EXTENSIONS = {'json'}
|
|
35
37
|
MAX_UPLOAD_SIZE = 16 * 1024 * 1024 # 16MB
|
|
36
38
|
|
|
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
|
+
"updateTopic": "update/wiliot/<gatewayId>",
|
|
46
|
+
"statusTopic": "status/wiliot/<gatewayId>",
|
|
47
|
+
"dataTopic": "data/wiliot/<gatewayId>"
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
app = Flask(__name__)
|
|
38
51
|
app.secret_key = os.urandom(24) # Required for sessions
|
|
39
52
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
@@ -43,6 +56,7 @@ app.config['MAX_CONTENT_LENGTH'] = MAX_UPLOAD_SIZE
|
|
|
43
56
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
44
57
|
os.makedirs(CONFIG_FOLDER, exist_ok=True)
|
|
45
58
|
os.makedirs(TEST_LIST_FOLDER, exist_ok=True)
|
|
59
|
+
os.makedirs(CUSTOM_BROKER_FOLDER, exist_ok=True)
|
|
46
60
|
|
|
47
61
|
# extend Jinja search path to include shared dir
|
|
48
62
|
app.jinja_loader = ChoiceLoader([
|
|
@@ -70,9 +84,32 @@ def get_mandatory_modules_for_flavor(flavor):
|
|
|
70
84
|
return [MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT]
|
|
71
85
|
return []
|
|
72
86
|
|
|
87
|
+
def get_mandatory_modules_by_schema(schema_data, flavor):
|
|
88
|
+
"""Get modules that are mandatory by schema (modules declared in schema)."""
|
|
89
|
+
if not schema_data or "modules" not in schema_data:
|
|
90
|
+
return []
|
|
91
|
+
|
|
92
|
+
# Only apply to bridge flavors (bridge_only and combo)
|
|
93
|
+
if flavor not in (FLAVOR_BRIDGE_ONLY, FLAVOR_COMBO):
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
schema_modules = schema_data.get("modules", {})
|
|
97
|
+
if not isinstance(schema_modules, dict):
|
|
98
|
+
return []
|
|
99
|
+
|
|
100
|
+
# Map schema module names to test module names
|
|
101
|
+
mandatory_by_schema = []
|
|
102
|
+
for schema_mod_name in schema_modules.keys():
|
|
103
|
+
# Map schema module name to test module name
|
|
104
|
+
test_mod_name = test_module_and_schema_module_to_other(schema_mod_name)
|
|
105
|
+
if test_mod_name not in mandatory_by_schema and test_mod_name != 'custom':
|
|
106
|
+
mandatory_by_schema.append(test_mod_name)
|
|
107
|
+
|
|
108
|
+
return mandatory_by_schema
|
|
109
|
+
|
|
73
110
|
# TODO: This is a temporary mapping to map test module names to schema module names - remove this
|
|
74
|
-
def
|
|
75
|
-
"""Map test module names
|
|
111
|
+
def test_module_and_schema_module_to_other(module_name):
|
|
112
|
+
"""Map test module names and schema module names to one another."""
|
|
76
113
|
mapping = {
|
|
77
114
|
"calibration": "calibration",
|
|
78
115
|
"datapath": "datapath",
|
|
@@ -82,7 +119,12 @@ def test_module_to_schema_module(test_module_name):
|
|
|
82
119
|
"sensors": "externalSensor", # different name
|
|
83
120
|
"custom": "custom"
|
|
84
121
|
}
|
|
85
|
-
|
|
122
|
+
for k, v in mapping.items():
|
|
123
|
+
if module_name == k:
|
|
124
|
+
return v
|
|
125
|
+
if module_name == v:
|
|
126
|
+
return k
|
|
127
|
+
return module_name # Return as-is if not in mapping
|
|
86
128
|
|
|
87
129
|
def filter_modules_by_flavor(tests_schema, flavor, schema_data=None):
|
|
88
130
|
"""Filter modules and tests based on selected flavor."""
|
|
@@ -115,12 +157,17 @@ def filter_modules_by_flavor(tests_schema, flavor, schema_data=None):
|
|
|
115
157
|
# Include all tests for combo
|
|
116
158
|
filtered_tests.append(test)
|
|
117
159
|
|
|
118
|
-
#
|
|
160
|
+
# Get modules mandatory by schema
|
|
161
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
|
|
162
|
+
is_mandatory_by_schema = mod_name in mandatory_by_schema
|
|
163
|
+
|
|
164
|
+
# Only include module if it has tests or is mandatory (by flavor or schema)
|
|
119
165
|
# 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:
|
|
166
|
+
if filtered_tests or mod_name in mandatory_modules or is_mandatory_by_schema:
|
|
121
167
|
mod_copy = mod.copy()
|
|
122
168
|
mod_copy["tests"] = filtered_tests
|
|
123
169
|
mod_copy["is_mandatory"] = mod_name in mandatory_modules
|
|
170
|
+
mod_copy["is_mandatory_by_schema"] = is_mandatory_by_schema
|
|
124
171
|
filtered_modules.append(mod_copy)
|
|
125
172
|
|
|
126
173
|
# Sort modules: cloud_connectivity first, then edge_mgmt, then by schema order
|
|
@@ -136,7 +183,7 @@ def filter_modules_by_flavor(tests_schema, flavor, schema_data=None):
|
|
|
136
183
|
if schema_module_order:
|
|
137
184
|
try:
|
|
138
185
|
# Map test module name to schema module name
|
|
139
|
-
schema_mod_name =
|
|
186
|
+
schema_mod_name = test_module_and_schema_module_to_other(mod_name)
|
|
140
187
|
idx = schema_module_order.index(schema_mod_name)
|
|
141
188
|
return (2, idx)
|
|
142
189
|
except ValueError:
|
|
@@ -285,30 +332,37 @@ def verify_schema_matches_selection(schema_data, flavor, selected_modules, selec
|
|
|
285
332
|
bridge_modules_to_check = [m for m in selected_modules if m not in (MODULE_CLOUD_CONNECTIVITY, MODULE_EDGE_MGMT)]
|
|
286
333
|
for test_mod_name in bridge_modules_to_check:
|
|
287
334
|
# Map test module name to schema module name
|
|
288
|
-
schema_mod_name =
|
|
335
|
+
schema_mod_name = test_module_and_schema_module_to_other(test_mod_name)
|
|
289
336
|
if schema_mod_name not in schema_module_names:
|
|
290
337
|
warnings.append(f"Module '{test_mod_name}' may not be supported according to the uploaded schema")
|
|
291
338
|
|
|
292
339
|
return len(errors) == 0, errors, warnings
|
|
293
340
|
|
|
294
|
-
def check_certification_status(flavor, selected_modules, selected_tests, tests_schema):
|
|
341
|
+
def check_certification_status(flavor, selected_modules, selected_tests, tests_schema, schema_data=None, unsterile_run=False):
|
|
295
342
|
"""Check if the selection qualifies for certification or is test-only."""
|
|
296
343
|
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
344
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
|
|
297
345
|
all_mandatory_tests = []
|
|
298
346
|
|
|
299
|
-
# Get
|
|
347
|
+
# Get selected module names (handle both dict and string formats)
|
|
348
|
+
selected_module_names = [m.get("name") if isinstance(m, dict) else m for m in selected_modules]
|
|
349
|
+
|
|
350
|
+
# Get all mandatory tests from ALL selected modules (not just mandatory modules)
|
|
351
|
+
# When a module is selected, its mandatory tests become mandatory
|
|
300
352
|
for mod in tests_schema.get("modules", []):
|
|
301
353
|
mod_name = mod.get("name", "")
|
|
302
|
-
if mod_name in
|
|
354
|
+
if mod_name in selected_module_names:
|
|
303
355
|
for test in mod.get("tests", []):
|
|
304
356
|
if test.get("meta", {}).get("mandatory", 0) == 1:
|
|
305
357
|
all_mandatory_tests.append(test.get("id"))
|
|
306
358
|
|
|
307
359
|
# 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
360
|
missing_modules = [m for m in mandatory_modules if m not in selected_module_names]
|
|
310
361
|
|
|
311
|
-
# Check if all mandatory
|
|
362
|
+
# Check if all schema-mandatory modules are selected
|
|
363
|
+
missing_modules_by_schema = [m for m in mandatory_by_schema if m not in selected_module_names]
|
|
364
|
+
|
|
365
|
+
# Check if all mandatory tests (from selected modules) are selected
|
|
312
366
|
missing_tests = [t for t in all_mandatory_tests if t not in selected_tests]
|
|
313
367
|
|
|
314
368
|
# Check for "at least one additional module" requirement for Bridge Only and Combo
|
|
@@ -319,9 +373,11 @@ def check_certification_status(flavor, selected_modules, selected_tests, tests_s
|
|
|
319
373
|
if len(additional_modules) == 0:
|
|
320
374
|
missing_additional_module = True
|
|
321
375
|
|
|
322
|
-
|
|
376
|
+
# A certifying run must be sterile - if unsterile_run is set, force non-certifying
|
|
377
|
+
is_certified = (len(missing_modules) == 0 and len(missing_modules_by_schema) == 0 and
|
|
378
|
+
len(missing_tests) == 0 and not missing_additional_module and not unsterile_run)
|
|
323
379
|
|
|
324
|
-
return is_certified, missing_modules, missing_tests, missing_additional_module
|
|
380
|
+
return is_certified, missing_modules, missing_tests, missing_additional_module, missing_modules_by_schema
|
|
325
381
|
|
|
326
382
|
def _prepare_form_data_for_template(session, cert_schema):
|
|
327
383
|
"""Prepare form_data for template, ensuring custom_broker_path is included if available."""
|
|
@@ -424,6 +480,43 @@ def _write_testlist(selected_ids: list[str], form_data=None, is_certified=False)
|
|
|
424
480
|
print(f"Custom test list saved in {path}")
|
|
425
481
|
return path
|
|
426
482
|
|
|
483
|
+
def _write_custom_broker_config(form_data, cert_schema_title="cert_run") -> str:
|
|
484
|
+
"""
|
|
485
|
+
Generate a custom broker JSON configuration file from form data.
|
|
486
|
+
Extracts broker field values and creates a JSON file matching hivemq.json format.
|
|
487
|
+
"""
|
|
488
|
+
# Extract broker configuration from form_data
|
|
489
|
+
broker_config = {}
|
|
490
|
+
|
|
491
|
+
# Field names follow pattern: {schema_title}_custom_broker_{field_name}
|
|
492
|
+
field_prefix = f"{cert_schema_title}_custom_broker_"
|
|
493
|
+
|
|
494
|
+
# Extract values from form_data, using defaults if not provided
|
|
495
|
+
for field in DEFAULT_CUSTOM_BROKER.keys():
|
|
496
|
+
field_name = f"{field_prefix}{field}"
|
|
497
|
+
value = form_data.get(field_name, "")
|
|
498
|
+
|
|
499
|
+
# Convert port to int if it's a number
|
|
500
|
+
if field == "port":
|
|
501
|
+
try:
|
|
502
|
+
broker_config[field] = int(value) if value else DEFAULT_CUSTOM_BROKER[field]
|
|
503
|
+
except (ValueError, TypeError):
|
|
504
|
+
broker_config[field] = DEFAULT_CUSTOM_BROKER[field]
|
|
505
|
+
else:
|
|
506
|
+
broker_config[field] = value
|
|
507
|
+
|
|
508
|
+
# Generate filename with timestamp
|
|
509
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
510
|
+
filename = f"custom_broker_{timestamp}.json"
|
|
511
|
+
filepath = os.path.join(CUSTOM_BROKER_FOLDER, filename)
|
|
512
|
+
|
|
513
|
+
# Write JSON file
|
|
514
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
515
|
+
json.dump(broker_config, f, indent=4)
|
|
516
|
+
|
|
517
|
+
print(f"Custom broker config saved in {filepath}")
|
|
518
|
+
return filepath
|
|
519
|
+
|
|
427
520
|
@app.route("/")
|
|
428
521
|
def index():
|
|
429
522
|
"""Redirect to step 1 or show current step."""
|
|
@@ -451,6 +544,7 @@ def step(step_num):
|
|
|
451
544
|
session['selected_modules'] = []
|
|
452
545
|
session['selected_tests'] = []
|
|
453
546
|
session['form_data'] = {}
|
|
547
|
+
session['schema_mandatory_initialized'] = False
|
|
454
548
|
|
|
455
549
|
title = "Run Certificate"
|
|
456
550
|
tests_schema = web_utils.scan_tests_dir(CERT_TESTS_ROOT)
|
|
@@ -505,7 +599,22 @@ def step(step_num):
|
|
|
505
599
|
flavor = session.get('flavor')
|
|
506
600
|
if 'selected_modules' not in session or not session.get('selected_modules'):
|
|
507
601
|
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
508
|
-
|
|
602
|
+
# Add schema-mandatory modules
|
|
603
|
+
schema_data = result # result is the validated schema data
|
|
604
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
|
|
605
|
+
all_mandatory = list(set(mandatory_modules + mandatory_by_schema))
|
|
606
|
+
session['selected_modules'] = all_mandatory
|
|
607
|
+
# Mark schema-mandatory modules as initialized
|
|
608
|
+
session['schema_mandatory_initialized'] = True
|
|
609
|
+
else:
|
|
610
|
+
# If modules already exist, add schema-mandatory modules and mark as initialized
|
|
611
|
+
schema_data = result
|
|
612
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
|
|
613
|
+
current_modules = session.get('selected_modules', [])
|
|
614
|
+
updated_modules = list(set(current_modules + mandatory_by_schema))
|
|
615
|
+
if len(updated_modules) > len(current_modules):
|
|
616
|
+
session['selected_modules'] = updated_modules
|
|
617
|
+
session['schema_mandatory_initialized'] = True
|
|
509
618
|
# For GW only, skip step 3 (modules)
|
|
510
619
|
if session.get('flavor') == FLAVOR_GW_ONLY:
|
|
511
620
|
session['current_step'] = 4
|
|
@@ -575,43 +684,19 @@ def step(step_num):
|
|
|
575
684
|
else:
|
|
576
685
|
form_data_dict[key] = request.form.get(key)
|
|
577
686
|
|
|
578
|
-
# Handle custom_broker
|
|
687
|
+
# Handle custom_broker configuration from form fields
|
|
579
688
|
cert_cli = CertificateCLI()
|
|
580
689
|
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
581
690
|
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
691
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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"
|
|
692
|
+
# Generate custom broker JSON file from form fields
|
|
693
|
+
try:
|
|
694
|
+
broker_config_path = _write_custom_broker_config(form_data_dict, cert_schema['title'])
|
|
695
|
+
normalized_path = os.path.normpath(broker_config_path)
|
|
696
|
+
session['custom_broker_path'] = normalized_path
|
|
697
|
+
form_data_dict[custom_broker_field_name] = normalized_path
|
|
698
|
+
except Exception as e:
|
|
699
|
+
error = f"Error generating custom broker configuration: {e}"
|
|
615
700
|
|
|
616
701
|
# Validate all required fields (required for moving forward, but allow going back)
|
|
617
702
|
if action == 'next':
|
|
@@ -631,13 +716,22 @@ def step(step_num):
|
|
|
631
716
|
field_name = f"{cert_schema['title']}_{field['name']}"
|
|
632
717
|
field_value = form_data_dict.get(field_name, '')
|
|
633
718
|
|
|
634
|
-
# Special handling for custom_broker
|
|
719
|
+
# Special handling for custom_broker - check if all required broker fields are filled
|
|
635
720
|
if field['name'] == 'custom_broker':
|
|
636
|
-
# Check
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if not
|
|
721
|
+
# Check all broker fields except username and password are filled
|
|
722
|
+
broker_field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
723
|
+
optional_fields = {'username', 'password'}
|
|
724
|
+
for broker_field_key in DEFAULT_CUSTOM_BROKER.keys():
|
|
725
|
+
if broker_field_key not in optional_fields:
|
|
726
|
+
broker_field_name = f"{broker_field_prefix}{broker_field_key}"
|
|
727
|
+
broker_field_value = form_data_dict.get(broker_field_name, '')
|
|
728
|
+
if not broker_field_value or (isinstance(broker_field_value, str) and not broker_field_value.strip()):
|
|
729
|
+
missing_required.append(f"custom_broker.{broker_field_key}")
|
|
730
|
+
break
|
|
731
|
+
# Also check if path was generated (should be generated above if fields are valid)
|
|
732
|
+
if not field_value and not session.get('custom_broker_path'):
|
|
733
|
+
# Only add if we haven't already added a broker field error
|
|
734
|
+
if not any('custom_broker.' in req for req in missing_required):
|
|
641
735
|
missing_required.append(field.get('name', field['label']))
|
|
642
736
|
else:
|
|
643
737
|
# For other fields, check if value is provided
|
|
@@ -681,9 +775,30 @@ def step(step_num):
|
|
|
681
775
|
# This ensures mandatory items are only pre-checked at the beginning
|
|
682
776
|
if flavor and not selected_modules:
|
|
683
777
|
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
684
|
-
|
|
778
|
+
# Add schema-mandatory modules if schema is available
|
|
779
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
780
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
|
|
781
|
+
all_mandatory = list(set(mandatory_modules + mandatory_by_schema))
|
|
782
|
+
selected_modules = all_mandatory
|
|
685
783
|
session['selected_modules'] = selected_modules
|
|
686
784
|
|
|
785
|
+
# Ensure schema-mandatory modules are added when first reaching step 3 (only if not already initialized)
|
|
786
|
+
# Use a session flag to track if schema-mandatory modules have been initialized
|
|
787
|
+
if step_num == 3 and flavor and schema_path:
|
|
788
|
+
schema_mandatory_initialized = session.get('schema_mandatory_initialized', False)
|
|
789
|
+
if not schema_mandatory_initialized:
|
|
790
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
791
|
+
if schema_data:
|
|
792
|
+
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor)
|
|
793
|
+
# Add schema-mandatory modules that aren't already selected
|
|
794
|
+
current_modules = session.get('selected_modules', [])
|
|
795
|
+
updated_modules = list(set(current_modules + mandatory_by_schema))
|
|
796
|
+
if len(updated_modules) > len(current_modules):
|
|
797
|
+
selected_modules = updated_modules
|
|
798
|
+
session['selected_modules'] = selected_modules
|
|
799
|
+
# Mark as initialized so we don't re-add them when going back
|
|
800
|
+
session['schema_mandatory_initialized'] = True
|
|
801
|
+
|
|
687
802
|
# Initialize mandatory tests only if not already in session and we have selected modules
|
|
688
803
|
# Only initialize when first reaching step 4 (not on every GET request)
|
|
689
804
|
if flavor and selected_modules and not selected_tests and step_num == 4:
|
|
@@ -714,17 +829,28 @@ def step(step_num):
|
|
|
714
829
|
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
715
830
|
filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data)
|
|
716
831
|
|
|
832
|
+
# Get CLI schemas for form fields - use certificate_cli for all flavors (needed early for unsterile_run check)
|
|
833
|
+
cert_cli = CertificateCLI()
|
|
834
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
835
|
+
|
|
836
|
+
# Check if unsterile_run is set in form_data
|
|
837
|
+
form_data = _prepare_form_data_for_template(session, cert_schema)
|
|
838
|
+
unsterile_run_field = f"{cert_schema['title']}_unsterile_run"
|
|
839
|
+
unsterile_run = bool(form_data.get(unsterile_run_field))
|
|
840
|
+
|
|
717
841
|
# Get certification status
|
|
718
842
|
is_certified = True
|
|
719
843
|
missing_modules = []
|
|
720
844
|
missing_tests = []
|
|
721
845
|
missing_tests_details = [] # List of dicts with test id and label
|
|
722
846
|
missing_additional_module = False
|
|
847
|
+
missing_modules_by_schema = []
|
|
723
848
|
if step_num >= 4 and flavor and selected_modules and selected_tests:
|
|
724
849
|
# Convert selected_modules to module dicts for check_certification_status
|
|
725
850
|
module_dicts = [m for m in filtered_modules if m.get('name') in selected_modules]
|
|
726
|
-
|
|
727
|
-
|
|
851
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
852
|
+
is_certified, missing_modules, missing_tests, missing_additional_module, missing_modules_by_schema = check_certification_status(
|
|
853
|
+
flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run
|
|
728
854
|
)
|
|
729
855
|
|
|
730
856
|
# Get test details (labels) for missing tests
|
|
@@ -751,10 +877,12 @@ def step(step_num):
|
|
|
751
877
|
)
|
|
752
878
|
schema_errors = errors
|
|
753
879
|
schema_warnings = warnings
|
|
880
|
+
# Get missing schema-mandatory modules for display (recalculate if not already set)
|
|
881
|
+
if not missing_modules_by_schema and step_num >= 4 and flavor and selected_modules:
|
|
882
|
+
_, _, _, _, missing_modules_by_schema = check_certification_status(
|
|
883
|
+
flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run
|
|
884
|
+
)
|
|
754
885
|
|
|
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
886
|
|
|
759
887
|
# Calculate total test count including mandatory tests from selected modules
|
|
760
888
|
total_test_count = len(selected_tests)
|
|
@@ -770,6 +898,8 @@ def step(step_num):
|
|
|
770
898
|
# Prepare data for JavaScript validation
|
|
771
899
|
# Always initialize these to avoid Undefined errors in template
|
|
772
900
|
mandatory_modules_for_js = get_mandatory_modules_for_flavor(flavor) if flavor else []
|
|
901
|
+
schema_data_for_js = load_schema_data(schema_path) if schema_path else None
|
|
902
|
+
mandatory_modules_by_schema_for_js = get_mandatory_modules_by_schema(schema_data_for_js, flavor) if (schema_data_for_js and flavor) else []
|
|
773
903
|
mandatory_tests_for_js = []
|
|
774
904
|
|
|
775
905
|
if step_num in [3, 4] and flavor:
|
|
@@ -839,16 +969,21 @@ def step(step_num):
|
|
|
839
969
|
missing_additional_module=missing_additional_module,
|
|
840
970
|
schema_errors=schema_errors,
|
|
841
971
|
schema_warnings=schema_warnings,
|
|
972
|
+
missing_modules_by_schema=missing_modules_by_schema,
|
|
842
973
|
mandatory_modules_for_js=mandatory_modules_for_js,
|
|
974
|
+
mandatory_modules_by_schema_for_js=mandatory_modules_by_schema_for_js,
|
|
843
975
|
mandatory_tests_for_js=mandatory_tests_for_js,
|
|
844
976
|
error=error,
|
|
845
977
|
warning=warning,
|
|
846
978
|
form_data=_prepare_form_data_for_template(session, cert_schema),
|
|
979
|
+
custom_broker_defaults=DEFAULT_CUSTOM_BROKER,
|
|
980
|
+
custom_broker_field_keys=list(DEFAULT_CUSTOM_BROKER.keys()),
|
|
847
981
|
run_completed=run_completed,
|
|
848
982
|
run_terminal=run_terminal,
|
|
849
983
|
run_pid=run_pid,
|
|
850
984
|
run_is_certified=run_is_certified,
|
|
851
|
-
available_ports=available_ports
|
|
985
|
+
available_ports=available_ports,
|
|
986
|
+
unsterile_run=unsterile_run)
|
|
852
987
|
|
|
853
988
|
def execute_certificate():
|
|
854
989
|
"""Execute the certificate run based on session data."""
|
|
@@ -874,7 +1009,13 @@ def execute_certificate():
|
|
|
874
1009
|
tests_schema = web_utils.scan_tests_dir(CERT_TESTS_ROOT)
|
|
875
1010
|
filtered_modules = filter_modules_by_flavor(tests_schema, flavor)
|
|
876
1011
|
module_dicts = [m for m in filtered_modules if m.get('name') in session.get('selected_modules', [])]
|
|
877
|
-
|
|
1012
|
+
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
1013
|
+
# Check if unsterile_run is set in form_data
|
|
1014
|
+
cert_cli = CertificateCLI()
|
|
1015
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1016
|
+
unsterile_run_field = f"{cert_schema['title']}_unsterile_run"
|
|
1017
|
+
unsterile_run = bool(form_data.get(unsterile_run_field))
|
|
1018
|
+
is_certified, _, _, _, _ = check_certification_status(flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run)
|
|
878
1019
|
|
|
879
1020
|
full_cmd = []
|
|
880
1021
|
|
|
@@ -937,6 +1078,17 @@ def export_config():
|
|
|
937
1078
|
flash("No configuration to export. Please complete at least step 1 and 2.", "error")
|
|
938
1079
|
return redirect(url_for('step', step_num=session.get('current_step', 1)))
|
|
939
1080
|
|
|
1081
|
+
# Extract custom broker form field values from form_data
|
|
1082
|
+
form_data = session.get('form_data', {})
|
|
1083
|
+
cert_cli = CertificateCLI()
|
|
1084
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1085
|
+
custom_broker_fields = {}
|
|
1086
|
+
field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
1087
|
+
for field_name in DEFAULT_CUSTOM_BROKER.keys():
|
|
1088
|
+
full_field_name = f"{field_prefix}{field_name}"
|
|
1089
|
+
if full_field_name in form_data:
|
|
1090
|
+
custom_broker_fields[field_name] = form_data[full_field_name]
|
|
1091
|
+
|
|
940
1092
|
# Prepare configuration data
|
|
941
1093
|
config = {
|
|
942
1094
|
'version': '1.0',
|
|
@@ -945,9 +1097,10 @@ def export_config():
|
|
|
945
1097
|
'schema_path': session.get('schema_path'),
|
|
946
1098
|
'selected_modules': session.get('selected_modules', []),
|
|
947
1099
|
'selected_tests': session.get('selected_tests', []),
|
|
948
|
-
'form_data':
|
|
1100
|
+
'form_data': form_data,
|
|
949
1101
|
'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
|
|
1102
|
+
'custom_broker_path': session.get('custom_broker_path'), # Include custom broker file path for backward compatibility
|
|
1103
|
+
'custom_broker_fields': custom_broker_fields # Include custom broker form field values
|
|
951
1104
|
}
|
|
952
1105
|
|
|
953
1106
|
# Create filename with timestamp
|
|
@@ -1008,17 +1161,47 @@ def import_config():
|
|
|
1008
1161
|
json.dump(schema_data, f, indent=2)
|
|
1009
1162
|
except Exception:
|
|
1010
1163
|
pass # If we can't save, user will need to re-upload
|
|
1011
|
-
|
|
1164
|
+
# Handle custom broker configuration
|
|
1165
|
+
custom_broker_fields = config_data.get('custom_broker_fields', {})
|
|
1166
|
+
if custom_broker_fields:
|
|
1167
|
+
# Restore broker form field values to form_data
|
|
1168
|
+
cert_cli = CertificateCLI()
|
|
1169
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1170
|
+
field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
1171
|
+
for field_name, field_value in custom_broker_fields.items():
|
|
1172
|
+
full_field_name = f"{field_prefix}{field_name}"
|
|
1173
|
+
session['form_data'][full_field_name] = field_value
|
|
1174
|
+
# Regenerate broker config file from form fields
|
|
1175
|
+
try:
|
|
1176
|
+
broker_config_path = _write_custom_broker_config(session['form_data'], cert_schema['title'])
|
|
1177
|
+
session['custom_broker_path'] = os.path.normpath(broker_config_path)
|
|
1178
|
+
except Exception:
|
|
1179
|
+
pass # If generation fails, user can reconfigure in step 4
|
|
1180
|
+
else:
|
|
1181
|
+
# Backward compatibility: try to use existing path
|
|
1182
|
+
session['custom_broker_path'] = config_data.get('custom_broker_path')
|
|
1012
1183
|
|
|
1013
1184
|
# Validate schema file still exists
|
|
1014
1185
|
if session['schema_path'] and not os.path.exists(session['schema_path']):
|
|
1015
1186
|
flash(f"Warning: Schema file not found at {session['schema_path']}. Please re-upload it in step 2.", "warning")
|
|
1016
1187
|
session['current_step'] = 2
|
|
1017
|
-
# Validate custom_broker file still exists if present
|
|
1188
|
+
# Validate custom_broker file still exists if present (or regenerate if we have fields)
|
|
1018
1189
|
elif session.get('custom_broker_path') and not os.path.exists(session['custom_broker_path']):
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1190
|
+
if custom_broker_fields:
|
|
1191
|
+
# Regenerate from fields if file doesn't exist
|
|
1192
|
+
try:
|
|
1193
|
+
cert_cli = CertificateCLI()
|
|
1194
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1195
|
+
broker_config_path = _write_custom_broker_config(session['form_data'], cert_schema['title'])
|
|
1196
|
+
session['custom_broker_path'] = os.path.normpath(broker_config_path)
|
|
1197
|
+
except Exception:
|
|
1198
|
+
flash(f"Warning: Custom broker configuration will be regenerated in step 4.", "warning")
|
|
1199
|
+
if session.get('current_step', 0) < 4:
|
|
1200
|
+
session['current_step'] = 4
|
|
1201
|
+
else:
|
|
1202
|
+
flash(f"Warning: Custom broker file not found at {session['custom_broker_path']}. Please reconfigure it in step 4.", "warning")
|
|
1203
|
+
if session.get('current_step', 0) < 4:
|
|
1204
|
+
session['current_step'] = 4
|
|
1022
1205
|
else:
|
|
1023
1206
|
# Determine appropriate step based on what's configured
|
|
1024
1207
|
if session.get('selected_tests'):
|
|
@@ -1047,6 +1230,8 @@ def import_config():
|
|
|
1047
1230
|
def clear_session():
|
|
1048
1231
|
"""Clear session and start fresh."""
|
|
1049
1232
|
session.clear()
|
|
1233
|
+
# Initialize session flags
|
|
1234
|
+
session['schema_mandatory_initialized'] = False
|
|
1050
1235
|
return redirect(url_for('step', step_num=1))
|
|
1051
1236
|
|
|
1052
1237
|
@app.route("/parser")
|