From b8398a36eb122fa64a8f253937258f6cdbb76ffc Mon Sep 17 00:00:00 2001 From: cproudlock Date: Mon, 10 Aug 2026 14:24:41 -0400 Subject: [PATCH] backups: an empty value in a .reg export must not fail the file A line of the form "Name=" with nothing after the sign is not strictly legal, but it occurs in real exports - the part marker's WJPRT.reg has KRelay1 like this. The parser raised on it, which failed the whole file, which meant that machine could never be backed up at all. Read it as an empty string so the value name is still preserved. --- plugins/backups/services/ntlars.py | 7 +++++ tests/test_plugins/test_backups.py | 47 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/plugins/backups/services/ntlars.py b/plugins/backups/services/ntlars.py index 593a75c..6e65ff9 100644 --- a/plugins/backups/services/ntlars.py +++ b/plugins/backups/services/ntlars.py @@ -149,6 +149,13 @@ def _parsevalue(body): return _parsehexvalue(body) if body == '-': return 'DELETE', None + if body == '': + # "Name=" with nothing after the sign. Not strictly legal, but it + # occurs in real exports (the part marker's WJPRT.reg has KRelay1 like + # this), and failing the whole file over it would mean that machine can + # never be backed up. Treat it as an empty string so the value NAME is + # still preserved. + return 'REG_SZ', '' raise NtlarsParseError('unrecognised value form: {!r}'.format(body)) diff --git a/tests/test_plugins/test_backups.py b/tests/test_plugins/test_backups.py index 6961d1d..da531aa 100644 --- a/tests/test_plugins/test_backups.py +++ b/tests/test_plugins/test_backups.py @@ -710,3 +710,50 @@ def test_panel_label_is_rendered_in_the_site_zone(bk_app, bk_plugin): revision = _db.session.get(BackupRevision, result['backuprevisionid']) # 16:30 UTC on 2026-08-07 is 12:30 EDT. assert '12:30' in _label(revision) + + +# ============================================================================= +# REGEDIT4 / part marker exports +# ============================================================================= + +REGEDIT4REG = ( + 'REGEDIT4\r\n\r\n' + r'[HKEY_LOCAL_MACHINE\SOFTWARE\GE Aircraft Engines\DNC\General]' '\r\n' + '"MachineNo"="WJPRT"\r\n' + '"Cnc"="MARKER"\r\n\r\n' + r'[HKEY_LOCAL_MACHINE\SOFTWARE\GE Aircraft Engines\DNC\Mark]' '\r\n' + '"Port Id"="COM4"\r\n' + '"Baud"="9600"\r\n' + '"KRelay1"=\r\n' +) + + +def test_regedit4_header_is_accepted(): + """The part marker's per-machine file is REGEDIT4, not 'Windows Registry + Editor Version 5.00'.""" + projection = ntlars.parse(REGEDIT4REG.encode('utf-8')) + assert registry.NtlarsKind.embeddedmachineno(projection) == 'WJPRT' + + +def test_value_with_an_empty_right_hand_side_does_not_fail_the_file(): + """A real export contains "KRelay1=" with nothing after the sign. Raising on + it would mean that machine could never be backed up at all.""" + projection = ntlars.parse(REGEDIT4REG.encode('utf-8')) + mark = next(k for k in projection['keys'] if k['path'] == 'Mark') + assert mark['values']['KRelay1'] == {'type': 'REG_SZ', 'data': ''} + + +def test_part_marker_serial_port_survives_the_roundtrip(): + """The COM port the marker hangs off is the point of keeping this config.""" + first = ntlars.parse(REGEDIT4REG.encode('utf-8')) + second = ntlars.parse(ntlars.render(first, dialect='wow6432node')) + mark = next(k for k in second['keys'] if k['path'] == 'Mark') + assert mark['values']['Port Id']['data'] == 'COM4' + + +def test_a_marker_export_shows_the_mark_tab(): + card = dncinfo.build(ntlars.parse(REGEDIT4REG.encode('utf-8')), assetid=0) + labels = [s['label'] for s in card['sections']] + assert any('MARK' in label for label in labels) + mark = next(s for s in card['sections'] if 'MARK' in s['label']) + assert any(f['label'] == 'Port Id' and f['value'] == 'COM4' for f in mark['fields'])