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
This commit is contained in:
None 2026-03-16 16:52:22 -05:00
parent 0d6979ce1d
commit 8624436367
2 changed files with 70 additions and 6 deletions

View file

@ -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

View file

@ -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', () => {