@@ -73,6 +73,7 @@ def __init__(self):
7373 self .ems_device = None
7474 self .gateway_device = None
7575 self .ems_slot_warned = set ()
76+ self .validation_parse_warned = set ()
7677 self ._discovery_report = None
7778 self ._now_utc_exact = datetime .now (timezone .utc )
7879 self .settings_from_cache = False
@@ -4064,6 +4065,28 @@ async def mock_publish(*args, **kwargs):
40644065 print ("ERROR: Expected a warning for unparseable validation, got {}" .format (ge_cloud .log_messages ))
40654066 return 1
40664067
4068+ # An empty option list must not parse as [""] and reject every value; it falls back to 'in:' too
4069+ ge_cloud .settings ["test123" ][90 ] = {"value" : "1" , "validation" : "Value must be one of: ()" , "validation_rules" : ["in:1,2" ]}
4070+ await ge_cloud .select_event (entity_id , "1" )
4071+ if write_calls != ["Export (AC3)" , "2" , "1" ]:
4072+ print ("ERROR: Expected empty option list to fall back to 'in:' and accept '1', got {}" .format (write_calls ))
4073+ return 1
4074+
4075+ # A non-string validation value is ignored rather than raising AttributeError
4076+ ge_cloud .settings ["test123" ][90 ] = {"value" : "1" , "validation" : {"unexpected" : True }, "validation_rules" : ["in:1,2" ]}
4077+ await ge_cloud .select_event (entity_id , "2" )
4078+ if write_calls != ["Export (AC3)" , "2" , "1" , "2" ]:
4079+ print ("ERROR: Expected non-string validation to fall back to 'in:' and accept '2', got {}" .format (write_calls ))
4080+ return 1
4081+
4082+ # Unparseable validation with no 'in:' rule has nothing to check against, so the write goes through
4083+ # and the GE Cloud API validates it server-side (the same as any select without option validation text)
4084+ ge_cloud .settings ["test123" ][90 ] = {"value" : "1" , "validation" : "Value must be one of:" , "validation_rules" : []}
4085+ await ge_cloud .select_event (entity_id , "5" )
4086+ if write_calls != ["Export (AC3)" , "2" , "1" , "2" , "5" ]:
4087+ print ("ERROR: Expected write without local validation when no options are known, got {}" .format (write_calls ))
4088+ return 1
4089+
40674090 return 0
40684091
40694092 return run_async (test ())
@@ -4076,6 +4099,13 @@ def _test_parse_validation_options(my_predbat):
40764099 cases = [
40774100 ("Value must be one of: (00:00, 00:30, 01:00)" , ["00:00" , "00:30" , "01:00" ]),
40784101 ("Value must be one of: (Eco (Paused), Timed Demand, Export (AC3))" , ["Eco (Paused)" , "Timed Demand" , "Export (AC3)" ]),
4102+ # Commas inside a label's own brackets do not split it
4103+ ("Value must be one of: (Eco, Pause (Battery, Grid), Export)" , ["Eco" , "Pause (Battery, Grid)" , "Export" ]),
4104+ ("Value must be one of: (0,1,2)" , ["0" , "1" , "2" ]),
4105+ # An empty option list is unparseable, not a single empty-string option
4106+ ("Value must be one of: ()" , None ),
4107+ ("Value must be one of: (" , None ),
4108+ ("Value must be one of: ( )" , None ),
40794109 ("Value must be one of:" , None ),
40804110 ("Value must be between 0 and 100" , None ),
40814111 ("" , None ),
@@ -4334,6 +4364,14 @@ def _test_publish_registers_bracketed_options(my_predbat):
43344364 print ("ERROR: Expected later registers to still be published" )
43354365 return 1
43364366
4367+ # A permanently unparseable register warns once, not on every settings refresh
4368+ run_async (ge_cloud .publish_registers ("test123" , registers ))
4369+ run_async (ge_cloud .publish_registers ("test123" , registers ))
4370+ warnings = [message for message in ge_cloud .log_messages if "Unable to parse options" in message ]
4371+ if len (warnings ) != 1 :
4372+ print ("ERROR: Expected exactly one unparseable-validation warning across three refreshes, got {}" .format (warnings ))
4373+ return 1
4374+
43374375 return 0
43384376
43394377
0 commit comments