Skip to content

Commit 8b927cb

Browse files
committed
fix: surface TreeXML parse failures to the discovery retry mechanism
When parseString failed on the buffered TreeXML response (truncated payload, encoding glitch, C-Gate restart mid-stream, etc.), the error was logged but nothing else: no retry was scheduled, no PAUSE state was set, and the network's discovery status sat in DISCOVERING forever even though no further work was happening. The watchdog had already been cleared when the 344 end-of-tree was received, so the stuck state was also invisible to diagnostics. Now the catch path calls _handleTreeRequestFailure with reason='parse error', which routes it through the same backoff retry budget used for 401 Network not found. After the retry limit is exhausted the network transitions to PAUSED with the standard recovery hint logged. Regression test added: feed broken XML to handleTreeData/handleTreeEnd, assert _treeRequestState records the failure and the initial backoff re-issues the gettree.
1 parent 4f2c1c1 commit 8b927cb

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

src/haDiscovery.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,11 @@ class HaDiscovery {
499499
line: err.line,
500500
column: err.column
501501
});
502+
// Surface the parse failure to the retry mechanism so a malformed
503+
// response from C-Gate (truncated, mid-restart, encoding glitch)
504+
// doesn't leave discovery silently stuck. Same backoff budget as
505+
// 401-on-tree applies; we'll PAUSE after the retry limit.
506+
this._handleTreeRequestFailure(networkForTree, `parse error: ${err.message || err}`);
502507
} else {
503508
this.logger.info(`Parsed TreeXML for network ${networkForTree} (took ${duration}ms)`);
504509

tests/haDiscovery.test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,39 @@ describe('HaDiscovery', () => {
555555
});
556556
});
557557

558+
describe('TreeXML parse failure recovery', () => {
559+
beforeEach(() => {
560+
jest.useFakeTimers();
561+
mockSettings.ha_discovery_networks = ['254'];
562+
});
563+
564+
afterEach(() => {
565+
haDiscovery.stop();
566+
jest.useRealTimers();
567+
});
568+
569+
it('engages the retry mechanism when parseString fails on malformed XML', () => {
570+
haDiscovery.trigger();
571+
expect(mockSendCommandFn).toHaveBeenCalledTimes(1);
572+
573+
// Simulate C-Gate returning a tree that's syntactically broken.
574+
haDiscovery.handleTreeStart('343 Begin tree //PROJECT/254');
575+
haDiscovery.handleTreeData('<not valid xml');
576+
haDiscovery.handleTreeEnd('344 End tree');
577+
578+
// Retry state for the network must now show one failed attempt,
579+
// proving the parseString failure was surfaced as a discovery
580+
// failure (not silently swallowed).
581+
const state = haDiscovery._treeRequestState.get('254');
582+
expect(state).toBeDefined();
583+
expect(state.attempts).toBe(1);
584+
585+
// Initial backoff fires and re-requests the tree.
586+
jest.advanceTimersByTime(haDiscovery._treeRetryInitialDelayMs);
587+
expect(mockSendCommandFn).toHaveBeenCalledTimes(2);
588+
});
589+
});
590+
558591
describe('TreeXML retry on startup race (401 Network not found)', () => {
559592
beforeEach(() => {
560593
jest.useFakeTimers();

0 commit comments

Comments
 (0)