"use strict"; // A SERVER payload shaped like the answer to getServerConfig. Every call // returns a fresh deep copy so tests can mutate it freely. const M3U_ID = "M-1-abc"; const XMLTV_ID = "X-1-def"; const XMLTV_FILE = XMLTV_ID + ".xml"; function channel(number, name, extra) { return Object.assign({ "_file.m3u.id": M3U_ID, "_file.m3u.name": "Provider A", "_file.m3u.path": "/config/data/" + M3U_ID + ".m3u", "_uuid.key": "CUID", "_uuid.value": "cuid-" + number, "group-title": "News", "name": name, "tvg-id": "", "tvg-logo": "http://logos.example/" + number + ".png", "tvg-name": name, "url": "http://stream.example/" + number, "x-active": true, "x-category": "", "x-channelID": String(number), "x-description": "", "x-group-title": "News", "x-mapping": "ch" + number + ".example", "x-name": name, "x-update-channel-icon": true, "x-update-channel-name": true, "x-xmltv-file": XMLTV_FILE, }, extra || {}); } function makeServer(overrides) { const server = { clientInfo: { arch: "darwin/arm64", os: "darwin", version: "2.5.0", uuid: "xteve-test-uuid", DVR: "192.168.1.10:34400", "m3u-url": "http://192.168.1.10:34400/m3u/xteve.m3u", "xepg-url": "http://192.168.1.10:34400/xmltv/xteve.xml", streams: "5", epgSource: "XEPG", xepg: "4", errors: "0", warnings: "1", }, settings: { "api": false, "authentication.api": false, "authentication.m3u": false, "authentication.pms": false, "authentication.web": false, "authentication.xml": false, "backup.keep": 10, "backup.path": "/config/backup/", "buffer": "ffmpeg", "buffer.size.kb": 1024, "buffer.timeout": 500, "cache.images": false, "epgSource": "XEPG", "ffmpeg.options": "-hide_banner -loglevel error -i [URL] -c copy -f mpegts pipe:1", "ffmpeg.path": "/usr/bin/ffmpeg", "files.update": true, "plex.token": "********", "plex.url": "http://plex.example:32400", "temp.path": "/tmp/xteve/", "tuner": 1, "udpxy": "", "update": ["0000", "1200"], "use_plexAPI": false, "user.agent": "xTeVe", "vlc.options": "--intf dummy --no-audio --no-video --sout '#standard{access=file,mux=ts,dst=-}' [URL]", "vlc.path": "/usr/bin/cvlc", "xepg.missing.epg.mode": "strict", "xepg.replace.missing.images": true, files: { m3u: { [M3U_ID]: { "compatibility": {"group.title": 100, "stream.id": 100, "streams": 5, "tvg.id": 80}, "counter.download": 3, "counter.error": 0, "description": "Main playlist", "file.source": "http://provider.example/list.m3u", "file.xteve": M3U_ID + ".m3u", "last.update": "2026-09-01 10:00:00", "name": "Provider A", "provider.availability": 100, "tuner": 2, "type": "m3u", }, }, hdhr: {}, xmltv: { [XMLTV_ID]: { "compatibility": {"xmltv.channels": 40, "xmltv.programs": 12000}, "counter.download": 3, "counter.error": 0, "description": "Main guide", "file.source": "http://provider.example/guide.xml", "file.xteve": XMLTV_FILE, "last.update": "2026-09-01 10:05:00", "name": "Guide A", "provider.availability": 100, "type": "xmltv", }, }, }, filter: {}, }, xepg: { epgMapping: { [M3U_ID + "-1"]: channel(1, "News 24"), [M3U_ID + "-2"]: channel(2, "x"), [M3U_ID + "-3"]: channel(3, "Sports HD", {"x-group-title": "Sport", "group-title": "Sport", "x-category": "Sports"}), [M3U_ID + "-4"]: channel(4, "Kids TV", {"x-xmltv-file": "xTeVe Dummy", "x-mapping": "30_Minutes"}), [M3U_ID + "-5"]: channel(5, "Unmapped", {"x-active": false, "x-xmltv-file": "-", "x-mapping": "-"}), }, xmltvMap: { [XMLTV_FILE]: { "ch1.example": {id: "ch1.example", "display-name": "News 24", icon: "http://logos.example/1.png"}, "ch2.example": {id: "ch2.example", "display-name": "Channel Two", icon: ""}, "ch3.example": {id: "ch3.example", "display-name": "Sports HD", icon: "http://logos.example/3.png"}, }, "xTeVe Dummy": { "30_Minutes": {id: "30_Minutes", "display-name": "30 Minutes", icon: ""}, "60_Minutes": {id: "60_Minutes", "display-name": "60 Minutes", icon: ""}, }, }, }, data: { StreamPreviewUI: {activeStreams: ["News 24", "Sports HD"], inactiveStreams: ["Unmapped"]}, playlist: {m3u: {groups: {text: ["News (2)", "Sport (1)"], value: ["News", "Sport"]}}}, }, users: {}, log: {log: {}}, status: true, }; const copy = JSON.parse(JSON.stringify(server)); if (overrides) { deepMerge(copy, overrides); } return copy; } function deepMerge(target, source) { for (const key of Object.keys(source)) { const value = source[key]; if (value && typeof value === "object" && !Array.isArray(value) && target[key] && typeof target[key] === "object") { deepMerge(target[key], value); } else { target[key] = value; } } } module.exports = {makeServer, M3U_ID, XMLTV_ID, XMLTV_FILE};