Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions test/data/xmltv/fixtures/invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"services": [
{
"id": 12346,
"networkId": 1,
"serviceId": 102,
"name": "非表示チャンネル",
"type": 0,
"logoId": -1,
"remoteControlKeyId": 6
}
],
"programs": []
}
14 changes: 14 additions & 0 deletions test/data/xmltv/fixtures/logo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"services": [
{
"id": 54321,
"networkId": 2,
"serviceId": 202,
"name": "ロゴチャンネル",
"type": 173,
"logoId": 10,
"remoteControlKeyId": 7
}
],
"programs": []
}
62 changes: 62 additions & 0 deletions test/data/xmltv/fixtures/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"services": [
{
"id": 12345,
"networkId": 1,
"serviceId": 101,
"name": "テストチャンネル & Co.",
"type": 1,
"logoId": -1,
"remoteControlKeyId": 5
},
{
"id": 12346,
"networkId": 1,
"serviceId": 102,
"name": "サブチャンネル",
"type": 1,
"logoId": -1,
"remoteControlKeyId": 5
},
{
"id": 12347,
"networkId": 1,
"serviceId": 999,
"name": "キーなしチャンネル",
"type": 173,
"logoId": -1
}
],
"programs": [
{
"id": 1000,
"networkId": 1,
"serviceId": 101,
"startAt": 1610000000000,
"duration": 3600000,
"name": "テスト番組 <こんにちは>",
"description": "テスト説明 & \"引用\" 'シングル'",
"genres": [
{ "lv1": 0, "lv2": 0, "un1": 0, "un2": 0 },
{ "lv1": 0, "lv2": 0, "un1": 0, "un2": 0 },
{ "lv1": 14, "lv2": 1, "un1": 2, "un2": 3 },
{ "lv1": 14, "lv2": 9, "un1": 9, "un2": 9 }
]
},
{
"id": 1001,
"networkId": 1,
"serviceId": 102,
"startAt": 1610003600000,
"duration": 1800000
},
{
"id": 1002,
"networkId": 1,
"serviceId": 9991,
"startAt": 1610005400000,
"duration": 1800000,
"name": "存在しないサービス"
}
]
}
4 changes: 4 additions & 0 deletions test/data/xmltv/goldens/empty.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tv SYSTEM "xmltv.dtd">
<tv source-info-name="Mirakurun">
</tv>
8 changes: 8 additions & 0 deletions test/data/xmltv/goldens/logo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tv SYSTEM "xmltv.dtd">
<tv source-info-name="Mirakurun">
<channel id="54321">
<display-name>ロゴチャンネル</display-name>
<display-name>7.1</display-name>
<icon src="http://localhost:40772/api/services/54321/logo" /></channel>
</tv>
26 changes: 26 additions & 0 deletions test/data/xmltv/goldens/valid.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tv SYSTEM "xmltv.dtd">
<tv source-info-name="Mirakurun">
<channel id="12345">
<display-name>テストチャンネル &amp; Co.</display-name>
<display-name>5.1</display-name>
</channel>
<channel id="12346">
<display-name>サブチャンネル</display-name>
<display-name>5.2</display-name>
</channel>
<channel id="12347">
<display-name>キーなしチャンネル</display-name>
<display-name>999.1</display-name>
</channel>
<programme start="20210107061320 +0000" stop="20210107071320 +0000" channel="12345">
<title>テスト番組 &lt;こんにちは&gt;</title>
<desc>テスト説明 &amp; &quot;引用&quot; &apos;シングル&apos;</desc>
<category>ニュース/報道 - 定時・総合</category>
<category>邦画 - サスペンス/ミステリー</category>
</programme>
<programme start="20210107071320 +0000" stop="20210107074320 +0000" channel="12346">
<title></title>
<desc></desc>
</programme>
</tv>
122 changes: 122 additions & 0 deletions test/xmltv.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const { describe, it, beforeEach, afterEach } = require("node:test");
const assert = require("assert");
const fs = require("fs");
const path = require("path");

// Mock dependencies before requiring the module
const _ = require("../lib/Mirakurun/_").default;
const ServiceClass = require("../lib/Mirakurun/Service").default;

// The file to test
const xmltv = require("../lib/Mirakurun/api/iptv/xmltv");

class MockResponse {
constructor() {
this.headers = {};
this.statusCode = 200;
this.body = "";
}
setHeader(name, value) {
this.headers[name] = value;
}
status(code) {
this.statusCode = code;
}
end(data) {
this.body += data;
}
}

function loadFixture(name) {
const jsonPath = path.join(__dirname, "data", "xmltv", "fixtures", `${name}.json`);
const data = JSON.parse(fs.readFileSync(jsonPath, "utf8"));

// Add getOrder function to services
data.services.forEach((s, i) => {
s.getOrder = () => i + 1;
});

return data;
}

function loadGolden(name) {
const xmlPath = path.join(__dirname, "data", "xmltv", "goldens", `${name}.xml`);
return fs.readFileSync(xmlPath, "utf8").trim();
}

describe("api/iptv/xmltv", () => {
let originalIsLogoDataExists;

beforeEach(() => {
_.service = {
items: [],
get: (networkId, serviceId) => {
return _.service.items.find(s => s.networkId === networkId && s.serviceId === serviceId) || null;
}
};
_.program = {
itemMap: new Map()
};

originalIsLogoDataExists = ServiceClass.isLogoDataExists;
ServiceClass.isLogoDataExists = async (networkId, logoId) => false;
});

afterEach(() => {
ServiceClass.isLogoDataExists = originalIsLogoDataExists;
});

it("should generate empty XMLTV when no services or programs exist", async () => {
const req = { protocol: "http", headers: { host: "localhost:40772" } };
const res = new MockResponse();

await xmltv.get(req, res);

assert.strictEqual(res.statusCode, 200);
assert.strictEqual(res.headers["Content-Type"], "text/xml; charset=utf-8");
assert.strictEqual(res.body, loadGolden("empty"));
});

it("should generate XMLTV with a valid service and program", async () => {
const req = { protocol: "http", headers: { host: "localhost:40772" } };
const res = new MockResponse();

const data = loadFixture("valid");
_.service.items = data.services;
for (const prog of data.programs) {
_.program.itemMap.set(prog.id, prog);
}

await xmltv.get(req, res);

assert.strictEqual(res.statusCode, 200);
assert.strictEqual(res.body, loadGolden("valid"));
});

it("should ignore services that are not type 1 or 173", async () => {
const req = { protocol: "http", headers: { host: "localhost:40772" } };
const res = new MockResponse();

const data = loadFixture("invalid");
_.service.items = data.services;

await xmltv.get(req, res);

assert.strictEqual(res.statusCode, 200);
assert.strictEqual(res.body, loadGolden("empty"));
});

it("should include logo tags if Service.isLogoDataExists is true", async () => {
ServiceClass.isLogoDataExists = async (networkId, logoId) => networkId === 2 && logoId === 10;

const req = { protocol: "http", headers: { host: "localhost:40772" } };
const res = new MockResponse();

const data = loadFixture("logo");
_.service.items = data.services;

await xmltv.get(req, res);

assert.strictEqual(res.body, loadGolden("logo"));
});
});