From 862443636722a5e333b63e302276bfee6570ab54 Mon Sep 17 00:00:00 2001 From: None <190783071+CodeBormen@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:52:22 -0500 Subject: [PATCH] fix(frontend): validate series_rules as array in settings parse/save - Add Array.isArray() guard in parseSettings for series_rules, matching the existing EPG ignore list pattern - Include series_rules in arrayFields list in getChangedSettings to prevent stringification during settings round-trip - Add 5 tests covering valid, corrupted, and missing series_rules data --- frontend/src/utils/pages/SettingsUtils.js | 14 +++-- .../pages/__tests__/SettingsUtils.test.js | 62 +++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/frontend/src/utils/pages/SettingsUtils.js b/frontend/src/utils/pages/SettingsUtils.js index cafe0d8c..aa32a8e7 100644 --- a/frontend/src/utils/pages/SettingsUtils.js +++ b/frontend/src/utils/pages/SettingsUtils.js @@ -174,11 +174,12 @@ export const saveChangedSettings = async (settings, changedSettings) => { export const getChangedSettings = (values, settings) => { const changedSettings = {}; - // EPG fields that should be kept as arrays - const epgFields = [ + // Fields that must remain as arrays and not be stringified + const arrayFields = [ 'epg_match_ignore_prefixes', 'epg_match_ignore_suffixes', 'epg_match_ignore_custom', + 'series_rules', ]; for (const settingKey in values) { @@ -199,12 +200,11 @@ export const getChangedSettings = (values, settings) => { continue; } - // Handle EPG fields specially - keep as arrays, don't skip empty arrays - if (epgFields.includes(settingKey)) { + // Handle array fields - keep as arrays, don't skip empty arrays + if (arrayFields.includes(settingKey)) { if (!Array.isArray(actualValue)) { actualValue = []; } - // Always include EPG fields in changes (even if empty) changedSettings[settingKey] = actualValue; continue; } @@ -299,7 +299,9 @@ export const parseSettings = (settings) => { typeof dvrSettings.post_offset_minutes === 'number' ? dvrSettings.post_offset_minutes : parseInt(dvrSettings.post_offset_minutes, 10) || 0; - parsed.series_rules = dvrSettings.series_rules; + parsed.series_rules = Array.isArray(dvrSettings.series_rules) + ? dvrSettings.series_rules + : []; } // Backup settings - direct mapping with underscore keys diff --git a/frontend/src/utils/pages/__tests__/SettingsUtils.test.js b/frontend/src/utils/pages/__tests__/SettingsUtils.test.js index b9efef94..9d4bb644 100644 --- a/frontend/src/utils/pages/__tests__/SettingsUtils.test.js +++ b/frontend/src/utils/pages/__tests__/SettingsUtils.test.js @@ -328,6 +328,51 @@ describe('SettingsUtils', () => { const result = SettingsUtils.parseSettings(mockSettings); expect(result.network_access).toEqual(['192.168.1.0/24', '10.0.0.0/8']); }); + + it('should parse valid series_rules as array', () => { + const mockSettings = { + dvr_settings: { + id: 2, + key: 'dvr_settings', + value: { + series_rules: [{ tvg_id: 'abc', mode: 'all', title: 'Show' }], + }, + }, + }; + + const result = SettingsUtils.parseSettings(mockSettings); + expect(result.series_rules).toEqual([ + { tvg_id: 'abc', mode: 'all', title: 'Show' }, + ]); + }); + + it('should default series_rules to empty array when not an array', () => { + const mockSettings = { + dvr_settings: { + id: 2, + key: 'dvr_settings', + value: { + series_rules: 'corrupted', + }, + }, + }; + + const result = SettingsUtils.parseSettings(mockSettings); + expect(result.series_rules).toEqual([]); + }); + + it('should default series_rules to empty array when missing', () => { + const mockSettings = { + dvr_settings: { + id: 2, + key: 'dvr_settings', + value: {}, + }, + }; + + const result = SettingsUtils.parseSettings(mockSettings); + expect(result.series_rules).toEqual([]); + }); }); describe('getChangedSettings', () => { @@ -447,6 +492,23 @@ describe('SettingsUtils', () => { expect(changes.epg_match_ignore_suffixes).toEqual([]); expect(changes.epg_match_ignore_custom).toEqual([]); }); + + it('should keep series_rules as array and not stringify', () => { + const rules = [{ tvg_id: 'abc', mode: 'all', title: 'Show' }]; + const values = { series_rules: rules }; + const settings = {}; + + const changes = SettingsUtils.getChangedSettings(values, settings); + expect(changes.series_rules).toEqual(rules); + }); + + it('should default series_rules to empty array if not an array', () => { + const values = { series_rules: 'corrupted' }; + const settings = {}; + + const changes = SettingsUtils.getChangedSettings(values, settings); + expect(changes.series_rules).toEqual([]); + }); }); describe('saveChangedSettings - EPG Mode', () => {