@zonuexe/techbook-mcp 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -1
- package/README.md +54 -22
- package/dist/adapters/http/fetch-client.d.ts.map +1 -1
- package/dist/adapters/http/fetch-client.js +18 -1
- package/dist/adapters/http/fetch-client.js.map +1 -1
- package/dist/adapters/openbd.d.ts.map +1 -1
- package/dist/adapters/openbd.js +18 -5
- package/dist/adapters/openbd.js.map +1 -1
- package/dist/adapters/publishers/base.d.ts +2 -1
- package/dist/adapters/publishers/base.d.ts.map +1 -1
- package/dist/adapters/publishers/base.js +8 -3
- package/dist/adapters/publishers/base.js.map +1 -1
- package/dist/adapters/publishers/cq-publishing.d.ts +3 -0
- package/dist/adapters/publishers/cq-publishing.d.ts.map +1 -0
- package/dist/adapters/publishers/cq-publishing.js +120 -0
- package/dist/adapters/publishers/cq-publishing.js.map +1 -0
- package/dist/adapters/publishers/google-books.d.ts +4 -0
- package/dist/adapters/publishers/google-books.d.ts.map +1 -0
- package/dist/adapters/publishers/google-books.js +76 -0
- package/dist/adapters/publishers/google-books.js.map +1 -0
- package/dist/adapters/publishers/isbn-publisher-codes.d.ts +21 -0
- package/dist/adapters/publishers/isbn-publisher-codes.d.ts.map +1 -0
- package/dist/adapters/publishers/isbn-publisher-codes.js +49 -0
- package/dist/adapters/publishers/isbn-publisher-codes.js.map +1 -0
- package/dist/adapters/publishers/juse-p.d.ts +3 -0
- package/dist/adapters/publishers/juse-p.d.ts.map +1 -0
- package/dist/adapters/publishers/juse-p.js +110 -0
- package/dist/adapters/publishers/juse-p.js.map +1 -0
- package/dist/adapters/publishers/leanpub.d.ts +3 -0
- package/dist/adapters/publishers/leanpub.d.ts.map +1 -0
- package/dist/adapters/publishers/leanpub.js +96 -0
- package/dist/adapters/publishers/leanpub.js.map +1 -0
- package/dist/adapters/publishers/oreilly-japan.d.ts.map +1 -1
- package/dist/adapters/publishers/oreilly-japan.js +8 -2
- package/dist/adapters/publishers/oreilly-japan.js.map +1 -1
- package/dist/adapters/publishers/peaks.d.ts.map +1 -1
- package/dist/adapters/publishers/peaks.js +3 -2
- package/dist/adapters/publishers/peaks.js.map +1 -1
- package/dist/adapters/publishers/personal-media.d.ts.map +1 -1
- package/dist/adapters/publishers/personal-media.js +3 -2
- package/dist/adapters/publishers/personal-media.js.map +1 -1
- package/dist/adapters/publishers/pragprog.d.ts +3 -0
- package/dist/adapters/publishers/pragprog.d.ts.map +1 -0
- package/dist/adapters/publishers/pragprog.js +120 -0
- package/dist/adapters/publishers/pragprog.js.map +1 -0
- package/dist/adapters/publishers/registry.d.ts.map +1 -1
- package/dist/adapters/publishers/registry.js +10 -0
- package/dist/adapters/publishers/registry.js.map +1 -1
- package/dist/adapters/publishers/techbookfest.d.ts.map +1 -1
- package/dist/adapters/publishers/techbookfest.js +2 -1
- package/dist/adapters/publishers/techbookfest.js.map +1 -1
- package/dist/application/concurrency.d.ts +16 -0
- package/dist/application/concurrency.d.ts.map +1 -0
- package/dist/application/concurrency.js +42 -0
- package/dist/application/concurrency.js.map +1 -0
- package/dist/application/get-book-by-isbn.d.ts +0 -8
- package/dist/application/get-book-by-isbn.d.ts.map +1 -1
- package/dist/application/get-book-by-isbn.js +64 -7
- package/dist/application/get-book-by-isbn.js.map +1 -1
- package/dist/application/get-book-detail.d.ts.map +1 -1
- package/dist/application/get-book-detail.js +3 -0
- package/dist/application/get-book-detail.js.map +1 -1
- package/dist/application/search-books.d.ts +16 -5
- package/dist/application/search-books.d.ts.map +1 -1
- package/dist/application/search-books.js +46 -9
- package/dist/application/search-books.js.map +1 -1
- package/dist/config/credentials.d.ts +8 -0
- package/dist/config/credentials.d.ts.map +1 -0
- package/dist/config/credentials.js +32 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/domain/authors.d.ts +7 -0
- package/dist/domain/authors.d.ts.map +1 -0
- package/dist/domain/authors.js +22 -0
- package/dist/domain/authors.js.map +1 -0
- package/dist/domain/book.d.ts +2 -0
- package/dist/domain/book.d.ts.map +1 -1
- package/dist/domain/isbn.d.ts +8 -0
- package/dist/domain/isbn.d.ts.map +1 -0
- package/dist/domain/isbn.js +16 -0
- package/dist/domain/isbn.js.map +1 -0
- package/dist/domain/publisher.d.ts +16 -0
- package/dist/domain/publisher.d.ts.map +1 -1
- package/dist/domain/text-match.d.ts +32 -0
- package/dist/domain/text-match.d.ts.map +1 -0
- package/dist/domain/text-match.js +84 -0
- package/dist/domain/text-match.js.map +1 -0
- package/dist/main.js +15 -1
- package/dist/main.js.map +1 -1
- package/dist/mcp/server.d.ts +6 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +40 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +9 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +43 -0
- package/dist/setup.js.map +1 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +9 -0
- package/dist/version.js.map +1 -0
- package/docs/design-doc.md +127 -7
- package/package.json +14 -15
- package/.claude/settings.local.json +0 -38
- package/.codex/skills/techbook-mcp-release-prep/SKILL.md +0 -105
- package/.github/workflows/test.yml +0 -72
- package/.oxlintrc.json +0 -12
- package/AGENTS.md +0 -100
- package/deno.json +0 -3
- package/src/adapters/cache/memory-cache.ts +0 -31
- package/src/adapters/cache/null-cache.ts +0 -8
- package/src/adapters/calil.ts +0 -57
- package/src/adapters/html/cheerio-parser.ts +0 -50
- package/src/adapters/http/fetch-client.ts +0 -47
- package/src/adapters/http/mock-client.ts +0 -77
- package/src/adapters/openbd.ts +0 -142
- package/src/adapters/publishers/base.ts +0 -279
- package/src/adapters/publishers/book-tech.ts +0 -117
- package/src/adapters/publishers/born-digital.ts +0 -143
- package/src/adapters/publishers/coronasha.ts +0 -139
- package/src/adapters/publishers/gihyo.ts +0 -120
- package/src/adapters/publishers/impress.ts +0 -103
- package/src/adapters/publishers/lambdanote.ts +0 -146
- package/src/adapters/publishers/manatee.ts +0 -113
- package/src/adapters/publishers/maruzen-publishing.ts +0 -129
- package/src/adapters/publishers/optronics.ts +0 -113
- package/src/adapters/publishers/oreilly-japan.ts +0 -133
- package/src/adapters/publishers/peaks.ts +0 -98
- package/src/adapters/publishers/personal-media.ts +0 -168
- package/src/adapters/publishers/registry.ts +0 -38
- package/src/adapters/publishers/rutles.ts +0 -149
- package/src/adapters/publishers/saiensu.ts +0 -136
- package/src/adapters/publishers/seshop.ts +0 -121
- package/src/adapters/publishers/tatsu-zine.ts +0 -142
- package/src/adapters/publishers/techbookfest.ts +0 -179
- package/src/application/get-book-by-isbn.ts +0 -50
- package/src/application/get-book-detail.ts +0 -40
- package/src/application/search-books.ts +0 -64
- package/src/domain/book.ts +0 -35
- package/src/domain/publisher.ts +0 -18
- package/src/main.ts +0 -14
- package/src/mcp/server.ts +0 -113
- package/src/mcp/tools.ts +0 -71
- package/src/ports/cache.ts +0 -5
- package/src/ports/html-parser.ts +0 -15
- package/src/ports/http.ts +0 -17
- package/tests/fixtures/book-tech-detail.html +0 -51
- package/tests/fixtures/book-tech-search.html +0 -91
- package/tests/fixtures/born-digital-detail.html +0 -62
- package/tests/fixtures/born-digital-search.html +0 -51
- package/tests/fixtures/calil-book.html +0 -987
- package/tests/fixtures/coronasha-detail.html +0 -41
- package/tests/fixtures/coronasha-search.html +0 -61
- package/tests/fixtures/gihyo-detail.html +0 -42
- package/tests/fixtures/gihyo-search.json +0 -54
- package/tests/fixtures/impress-detail-epub.html +0 -746
- package/tests/fixtures/impress-detail-social.html +0 -689
- package/tests/fixtures/lambdanote-search.html +0 -66
- package/tests/fixtures/manatee-detail.html +0 -53
- package/tests/fixtures/manatee-search.html +0 -59
- package/tests/fixtures/maruzen-detail.html +0 -51
- package/tests/fixtures/maruzen-search.html +0 -60
- package/tests/fixtures/openbd-response.json +0 -110
- package/tests/fixtures/optronics-detail.html +0 -30
- package/tests/fixtures/optronics-search.html +0 -75
- package/tests/fixtures/oreilly-detail.html +0 -52
- package/tests/fixtures/oreilly-ebook-list.html +0 -53
- package/tests/fixtures/peaks-detail.html +0 -39
- package/tests/fixtures/peaks-top.html +0 -50
- package/tests/fixtures/personal-media-detail.html +0 -32
- package/tests/fixtures/personal-media-search.html +0 -39
- package/tests/fixtures/rutles-detail.html +0 -32
- package/tests/fixtures/rutles-search.html +0 -62
- package/tests/fixtures/saiensu-detail.html +0 -41
- package/tests/fixtures/saiensu-search.html +0 -65
- package/tests/fixtures/seshop-detail.html +0 -45
- package/tests/fixtures/seshop-search.html +0 -58
- package/tests/fixtures/tatsu-zine-detail-free.html +0 -24
- package/tests/fixtures/tatsu-zine-search.html +0 -40
- package/tests/fixtures/techbookfest-search.json +0 -73
- package/tests/unit/adapters/base.test.ts +0 -441
- package/tests/unit/adapters/calil.test.ts +0 -69
- package/tests/unit/adapters/openbd.test.ts +0 -185
- package/tests/unit/adapters/publishers/book-tech.test.ts +0 -186
- package/tests/unit/adapters/publishers/born-digital.test.ts +0 -194
- package/tests/unit/adapters/publishers/coronasha.test.ts +0 -207
- package/tests/unit/adapters/publishers/gihyo.test.ts +0 -137
- package/tests/unit/adapters/publishers/impress.test.ts +0 -129
- package/tests/unit/adapters/publishers/lambdanote.test.ts +0 -85
- package/tests/unit/adapters/publishers/manatee.test.ts +0 -165
- package/tests/unit/adapters/publishers/maruzen-publishing.test.ts +0 -179
- package/tests/unit/adapters/publishers/optronics.test.ts +0 -208
- package/tests/unit/adapters/publishers/oreilly-japan.test.ts +0 -194
- package/tests/unit/adapters/publishers/peaks.test.ts +0 -177
- package/tests/unit/adapters/publishers/personal-media.test.ts +0 -199
- package/tests/unit/adapters/publishers/rutles.test.ts +0 -173
- package/tests/unit/adapters/publishers/saiensu.test.ts +0 -169
- package/tests/unit/adapters/publishers/seshop.test.ts +0 -174
- package/tests/unit/adapters/publishers/tatsu-zine.test.ts +0 -172
- package/tests/unit/adapters/publishers/techbookfest.test.ts +0 -94
- package/tests/unit/adapters/registry.test.ts +0 -37
- package/tests/unit/application/get-book-by-isbn.test.ts +0 -176
- package/tests/unit/application/get-book-detail.test.ts +0 -102
- package/tests/unit/application/search-books.test.ts +0 -137
- package/tsconfig.json +0 -17
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { saiensuAdapter } from "../../../../src/adapters/publishers/saiensu.js";
|
|
6
|
-
import { MockHttpClient } from "../../../../src/adapters/http/mock-client.js";
|
|
7
|
-
import { CheerioHtmlParser } from "../../../../src/adapters/html/cheerio-parser.js";
|
|
8
|
-
import { NullCacheStore } from "../../../../src/adapters/cache/null-cache.js";
|
|
9
|
-
|
|
10
|
-
const FIXTURES_DIR = join(import.meta.dirname, "../../../fixtures");
|
|
11
|
-
|
|
12
|
-
function makeDeps(http: MockHttpClient) {
|
|
13
|
-
return { http, parser: new CheerioHtmlParser(), cache: new NullCacheStore() };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function loadFixture(name: string): Promise<string> {
|
|
17
|
-
return readFile(join(FIXTURES_DIR, name), "utf-8");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("saiensuAdapter", () => {
|
|
21
|
-
describe("search()", () => {
|
|
22
|
-
it("電子書籍のみ BookRecord[] を返す(紙は除外)", async () => {
|
|
23
|
-
const body = await loadFixture("saiensu-search.html");
|
|
24
|
-
const http = new MockHttpClient().addResponse(
|
|
25
|
-
"https://www.saiensu.co.jp/search/",
|
|
26
|
-
{ status: 200, body },
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const results = await saiensuAdapter.search({ title: "統計" }, makeDeps(http));
|
|
30
|
-
|
|
31
|
-
// フィクスチャには電子1件・紙1件あり、電子のみ返す
|
|
32
|
-
assert.strictEqual(results.length, 1);
|
|
33
|
-
assert.partialDeepStrictEqual(results[0], {
|
|
34
|
-
title: "統計リテラシーI【電子版】 ―記述統計から推測統計へ",
|
|
35
|
-
authors: ["堀井俊佑"],
|
|
36
|
-
publisher: "サイエンス社",
|
|
37
|
-
url: "https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
38
|
-
isbn: "9784781990491",
|
|
39
|
-
price: 2695,
|
|
40
|
-
publishedAt: "2026-03-25",
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("coverImageUrl が設定される", async () => {
|
|
45
|
-
const body = await loadFixture("saiensu-search.html");
|
|
46
|
-
const http = new MockHttpClient().addResponse(
|
|
47
|
-
"https://www.saiensu.co.jp/search/",
|
|
48
|
-
{ status: 200, body },
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const results = await saiensuAdapter.search({ title: "統計" }, makeDeps(http));
|
|
52
|
-
|
|
53
|
-
assert.strictEqual(
|
|
54
|
-
results[0].coverImageUrl,
|
|
55
|
-
"https://www.saiensu.co.jp/bookThumbs/2026-978-4-7819-9049-1.jpg",
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("ebookStores にサイエンス社(DRM付き)が含まれる", async () => {
|
|
60
|
-
const body = await loadFixture("saiensu-search.html");
|
|
61
|
-
const http = new MockHttpClient().addResponse(
|
|
62
|
-
"https://www.saiensu.co.jp/search/",
|
|
63
|
-
{ status: 200, body },
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const results = await saiensuAdapter.search({ title: "統計" }, makeDeps(http));
|
|
67
|
-
|
|
68
|
-
assert.deepStrictEqual(results[0].ebookStores, [
|
|
69
|
-
{
|
|
70
|
-
name: "サイエンス社",
|
|
71
|
-
url: "https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
72
|
-
drm: "password_pdf",
|
|
73
|
-
},
|
|
74
|
-
]);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("著者の所属・役割語を除去する", async () => {
|
|
78
|
-
const body = await loadFixture("saiensu-search.html");
|
|
79
|
-
const http = new MockHttpClient().addResponse(
|
|
80
|
-
"https://www.saiensu.co.jp/search/",
|
|
81
|
-
{ status: 200, body },
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const results = await saiensuAdapter.search({ title: "統計" }, makeDeps(http));
|
|
85
|
-
|
|
86
|
-
assert.deepStrictEqual(results[0].authors, ["堀井俊佑"]);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("title も author も空の場合は [] を返しHTTPを呼ばない", async () => {
|
|
90
|
-
const http = new MockHttpClient();
|
|
91
|
-
|
|
92
|
-
const results = await saiensuAdapter.search({}, makeDeps(http));
|
|
93
|
-
|
|
94
|
-
assert.deepStrictEqual(results, []);
|
|
95
|
-
assert.strictEqual(http.calls.length, 0);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("検索リクエストに keyword が含まれる", async () => {
|
|
99
|
-
const body = await loadFixture("saiensu-search.html");
|
|
100
|
-
const http = new MockHttpClient().addResponse(
|
|
101
|
-
"https://www.saiensu.co.jp/search/",
|
|
102
|
-
{ status: 200, body },
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
await saiensuAdapter.search({ title: "意味論" }, makeDeps(http));
|
|
106
|
-
|
|
107
|
-
assert.ok(http.calls[0].includes("keyword=%E6%84%8F%E5%91%B3%E8%AB%96"));
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe("getDetail()", () => {
|
|
112
|
-
it("詳細情報を返す", async () => {
|
|
113
|
-
const body = await loadFixture("saiensu-detail.html");
|
|
114
|
-
const http = new MockHttpClient().addResponse(
|
|
115
|
-
"https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
116
|
-
{ status: 200, body },
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const book = await saiensuAdapter.getDetail(
|
|
120
|
-
"https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
121
|
-
makeDeps(http),
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
assert.partialDeepStrictEqual(book, {
|
|
125
|
-
title: "統計リテラシーI【電子版】 ―記述統計から推測統計へ",
|
|
126
|
-
publisher: "サイエンス社",
|
|
127
|
-
isbn: "9784781990491",
|
|
128
|
-
price: 2695,
|
|
129
|
-
publishedAt: "2026-03-25",
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("著者の所属・役割語を除去する", async () => {
|
|
134
|
-
const body = await loadFixture("saiensu-detail.html");
|
|
135
|
-
const http = new MockHttpClient().addResponse(
|
|
136
|
-
"https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
137
|
-
{ status: 200, body },
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const book = await saiensuAdapter.getDetail(
|
|
141
|
-
"https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
142
|
-
makeDeps(http),
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
assert.deepStrictEqual(book.authors, ["堀井俊佑"]);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("ebookStores にサイエンス社(DRM付き)が含まれる", async () => {
|
|
149
|
-
const body = await loadFixture("saiensu-detail.html");
|
|
150
|
-
const http = new MockHttpClient().addResponse(
|
|
151
|
-
"https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
152
|
-
{ status: 200, body },
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
const book = await saiensuAdapter.getDetail(
|
|
156
|
-
"https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
157
|
-
makeDeps(http),
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
assert.deepStrictEqual(book.ebookStores, [
|
|
161
|
-
{
|
|
162
|
-
name: "サイエンス社",
|
|
163
|
-
url: "https://www.saiensu.co.jp/search/?isbn=978-4-7819-9049-1&y=2026",
|
|
164
|
-
drm: "password_pdf",
|
|
165
|
-
},
|
|
166
|
-
]);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { seshopAdapter } from "../../../../src/adapters/publishers/seshop.js";
|
|
6
|
-
import { MockHttpClient } from "../../../../src/adapters/http/mock-client.js";
|
|
7
|
-
import { CheerioHtmlParser } from "../../../../src/adapters/html/cheerio-parser.js";
|
|
8
|
-
import { NullCacheStore } from "../../../../src/adapters/cache/null-cache.js";
|
|
9
|
-
|
|
10
|
-
const FIXTURES_DIR = join(import.meta.dirname, "../../../fixtures");
|
|
11
|
-
|
|
12
|
-
function makeDeps(http: MockHttpClient) {
|
|
13
|
-
return { http, parser: new CheerioHtmlParser(), cache: new NullCacheStore() };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function loadFixture(name: string): Promise<string> {
|
|
17
|
-
return readFile(join(FIXTURES_DIR, name), "utf-8");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("seshopAdapter", () => {
|
|
21
|
-
describe("search()", () => {
|
|
22
|
-
it("電子書籍のみ BookRecord[] を返す(紙書籍は除外)", async () => {
|
|
23
|
-
const body = await loadFixture("seshop-search.html");
|
|
24
|
-
const http = new MockHttpClient().addResponse(
|
|
25
|
-
"https://www.seshop.com/search",
|
|
26
|
-
{ status: 200, body },
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const results = await seshopAdapter.search({ title: "TypeScript" }, makeDeps(http));
|
|
30
|
-
|
|
31
|
-
// フィクスチャには電子2件・紙1件あり、電子のみ返す
|
|
32
|
-
assert.strictEqual(results.length, 2);
|
|
33
|
-
assert.partialDeepStrictEqual(results[0], {
|
|
34
|
-
title: "TypeScript入門【PDF版】",
|
|
35
|
-
publisher: "翔泳社",
|
|
36
|
-
url: "https://www.seshop.com/product/detail/26500",
|
|
37
|
-
price: 3520,
|
|
38
|
-
publishedAt: "2024-06-10",
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("coverImageUrl が設定される", async () => {
|
|
43
|
-
const body = await loadFixture("seshop-search.html");
|
|
44
|
-
const http = new MockHttpClient().addResponse(
|
|
45
|
-
"https://www.seshop.com/search",
|
|
46
|
-
{ status: 200, body },
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const results = await seshopAdapter.search({ title: "TypeScript" }, makeDeps(http));
|
|
50
|
-
|
|
51
|
-
assert.strictEqual(
|
|
52
|
-
results[0].coverImageUrl,
|
|
53
|
-
"https://www.seshop.com/static/images/product/26500/L.png",
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("ebookStores に SEshop (social DRM) が含まれる", async () => {
|
|
58
|
-
const body = await loadFixture("seshop-search.html");
|
|
59
|
-
const http = new MockHttpClient().addResponse(
|
|
60
|
-
"https://www.seshop.com/search",
|
|
61
|
-
{ status: 200, body },
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const results = await seshopAdapter.search({ title: "TypeScript" }, makeDeps(http));
|
|
65
|
-
|
|
66
|
-
assert.deepStrictEqual(results[0].ebookStores, [
|
|
67
|
-
{
|
|
68
|
-
name: "SEshop",
|
|
69
|
-
url: "https://www.seshop.com/product/detail/26500",
|
|
70
|
-
drm: "social",
|
|
71
|
-
},
|
|
72
|
-
]);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("title も author も空の場合は [] を返しHTTPを呼ばない", async () => {
|
|
76
|
-
const http = new MockHttpClient();
|
|
77
|
-
|
|
78
|
-
const results = await seshopAdapter.search({}, makeDeps(http));
|
|
79
|
-
|
|
80
|
-
assert.deepStrictEqual(results, []);
|
|
81
|
-
assert.strictEqual(http.calls.length, 0);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("検索リクエストに keyword と category_id=327 が含まれる", async () => {
|
|
85
|
-
const body = await loadFixture("seshop-search.html");
|
|
86
|
-
const http = new MockHttpClient().addResponse(
|
|
87
|
-
"https://www.seshop.com/search",
|
|
88
|
-
{ status: 200, body },
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
await seshopAdapter.search({ title: "TypeScript" }, makeDeps(http));
|
|
92
|
-
|
|
93
|
-
assert.ok(http.calls[0].includes("keyword=TypeScript"));
|
|
94
|
-
assert.ok(http.calls[0].includes("category_id=327"));
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe("getDetail()", () => {
|
|
99
|
-
it("詳細情報を返す", async () => {
|
|
100
|
-
const body = await loadFixture("seshop-detail.html");
|
|
101
|
-
const http = new MockHttpClient().addResponse(
|
|
102
|
-
"https://www.seshop.com/product/detail/26500",
|
|
103
|
-
{ status: 200, body },
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const book = await seshopAdapter.getDetail(
|
|
107
|
-
"https://www.seshop.com/product/detail/26500",
|
|
108
|
-
makeDeps(http),
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
assert.partialDeepStrictEqual(book, {
|
|
112
|
-
title: "TypeScript入門【PDF版】",
|
|
113
|
-
publisher: "翔泳社",
|
|
114
|
-
isbn: "9784798190014",
|
|
115
|
-
price: 3520,
|
|
116
|
-
publishedAt: "2024-06-10",
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("複数著者を取得する", async () => {
|
|
121
|
-
const body = await loadFixture("seshop-detail.html");
|
|
122
|
-
const http = new MockHttpClient().addResponse(
|
|
123
|
-
"https://www.seshop.com/product/detail/26500",
|
|
124
|
-
{ status: 200, body },
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
const book = await seshopAdapter.getDetail(
|
|
128
|
-
"https://www.seshop.com/product/detail/26500",
|
|
129
|
-
makeDeps(http),
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
assert.deepStrictEqual(book.authors, ["山田 太郎", "鈴木 花子"]);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("coverImageUrl が設定される", async () => {
|
|
136
|
-
const body = await loadFixture("seshop-detail.html");
|
|
137
|
-
const http = new MockHttpClient().addResponse(
|
|
138
|
-
"https://www.seshop.com/product/detail/26500",
|
|
139
|
-
{ status: 200, body },
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
const book = await seshopAdapter.getDetail(
|
|
143
|
-
"https://www.seshop.com/product/detail/26500",
|
|
144
|
-
makeDeps(http),
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
assert.strictEqual(
|
|
148
|
-
book.coverImageUrl,
|
|
149
|
-
"https://www.seshop.com/static/images/product/26500/L.png",
|
|
150
|
-
);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("ebookStores に SEshop (social DRM) が含まれる", async () => {
|
|
154
|
-
const body = await loadFixture("seshop-detail.html");
|
|
155
|
-
const http = new MockHttpClient().addResponse(
|
|
156
|
-
"https://www.seshop.com/product/detail/26500",
|
|
157
|
-
{ status: 200, body },
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
const book = await seshopAdapter.getDetail(
|
|
161
|
-
"https://www.seshop.com/product/detail/26500",
|
|
162
|
-
makeDeps(http),
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
assert.deepStrictEqual(book.ebookStores, [
|
|
166
|
-
{
|
|
167
|
-
name: "SEshop",
|
|
168
|
-
url: "https://www.seshop.com/product/detail/26500",
|
|
169
|
-
drm: "social",
|
|
170
|
-
},
|
|
171
|
-
]);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
});
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { tatsuZineAdapter } from "../../../../src/adapters/publishers/tatsu-zine.js";
|
|
6
|
-
import { MockHttpClient } from "../../../../src/adapters/http/mock-client.js";
|
|
7
|
-
import { CheerioHtmlParser } from "../../../../src/adapters/html/cheerio-parser.js";
|
|
8
|
-
import { NullCacheStore } from "../../../../src/adapters/cache/null-cache.js";
|
|
9
|
-
|
|
10
|
-
const FIXTURES_DIR = join(import.meta.dirname, "../../../fixtures");
|
|
11
|
-
|
|
12
|
-
function makeDeps(http: MockHttpClient) {
|
|
13
|
-
return { http, parser: new CheerioHtmlParser(), cache: new NullCacheStore() };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function loadFixture(name: string): Promise<string> {
|
|
17
|
-
return readFile(join(FIXTURES_DIR, name), "utf-8");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("tatsuZineAdapter", () => {
|
|
21
|
-
describe("search()", () => {
|
|
22
|
-
it("検索結果から BookRecord[] を返す", async () => {
|
|
23
|
-
const body = await loadFixture("tatsu-zine-search.html");
|
|
24
|
-
const http = new MockHttpClient().addResponse(
|
|
25
|
-
"https://tatsu-zine.com/books/",
|
|
26
|
-
{ status: 200, body },
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const results = await tatsuZineAdapter.search({ title: "Go" }, makeDeps(http));
|
|
30
|
-
|
|
31
|
-
assert.strictEqual(results.length, 2);
|
|
32
|
-
assert.partialDeepStrictEqual(results[0], {
|
|
33
|
-
title: "Goプログラミング実践入門",
|
|
34
|
-
authors: ["Sau Sheong Chang", "武舎 広幸"],
|
|
35
|
-
publisher: "達人出版会",
|
|
36
|
-
});
|
|
37
|
-
assert.strictEqual(results[0].url, "https://tatsu-zine.com/books/go-programming");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("ebookStores に達人出版会(ソーシャルDRM)が含まれる", async () => {
|
|
41
|
-
const body = await loadFixture("tatsu-zine-search.html");
|
|
42
|
-
const http = new MockHttpClient().addResponse(
|
|
43
|
-
"https://tatsu-zine.com/books/",
|
|
44
|
-
{ status: 200, body },
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const results = await tatsuZineAdapter.search({ title: "Go" }, makeDeps(http));
|
|
48
|
-
|
|
49
|
-
assert.deepStrictEqual(results[0].ebookStores, [
|
|
50
|
-
{ name: "達人出版会", url: "https://tatsu-zine.com/books/go-programming", drm: "social" },
|
|
51
|
-
]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("limit を適用する", async () => {
|
|
55
|
-
const body = await loadFixture("tatsu-zine-search.html");
|
|
56
|
-
const http = new MockHttpClient().addResponse(
|
|
57
|
-
"https://tatsu-zine.com/books/",
|
|
58
|
-
{ status: 200, body },
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const results = await tatsuZineAdapter.search({ title: "Go", limit: 1 }, makeDeps(http));
|
|
62
|
-
|
|
63
|
-
assert.strictEqual(results.length, 1);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("title が未指定の場合は [] を返しHTTPを呼ばない", async () => {
|
|
67
|
-
const http = new MockHttpClient();
|
|
68
|
-
const results = await tatsuZineAdapter.search({}, makeDeps(http));
|
|
69
|
-
|
|
70
|
-
assert.deepStrictEqual(results, []);
|
|
71
|
-
assert.strictEqual(http.calls.length, 0);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("author のみ指定の場合も [] を返しHTTPを呼ばない", async () => {
|
|
75
|
-
const http = new MockHttpClient();
|
|
76
|
-
const results = await tatsuZineAdapter.search({ author: "Jesse Storimer" }, makeDeps(http));
|
|
77
|
-
|
|
78
|
-
assert.deepStrictEqual(results, []);
|
|
79
|
-
assert.strictEqual(http.calls.length, 0);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("書籍一覧ページ全体を取得してタイトルでフィルタする", async () => {
|
|
83
|
-
const body = await loadFixture("tatsu-zine-search.html");
|
|
84
|
-
const http = new MockHttpClient().addResponse(
|
|
85
|
-
"https://tatsu-zine.com/books/",
|
|
86
|
-
{ status: 200, body },
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
await tatsuZineAdapter.search({ title: "Go言語" }, makeDeps(http));
|
|
90
|
-
|
|
91
|
-
assert.strictEqual(http.calls[0], "https://tatsu-zine.com/books/");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("ページネーションがある場合は全ページを取得してフィルタする", async () => {
|
|
95
|
-
const page1 = `<!DOCTYPE html><html><body>
|
|
96
|
-
<section class="pagination">
|
|
97
|
-
<nav class="pagination">
|
|
98
|
-
<a class="btn-pagination" href="/books?page=2">2</a>
|
|
99
|
-
<a class="btn-pagination" href="/books?page=2">最後へ</a>
|
|
100
|
-
</nav>
|
|
101
|
-
</section>
|
|
102
|
-
<article class="book">
|
|
103
|
-
<h3 itemprop="name"><a href="/books/page1-book">ページ1の本</a></h3>
|
|
104
|
-
<p itemprop="author" class="author">著者A(著)</p>
|
|
105
|
-
</article>
|
|
106
|
-
</body></html>`;
|
|
107
|
-
const page2 = `<!DOCTYPE html><html><body>
|
|
108
|
-
<article class="book">
|
|
109
|
-
<h3 itemprop="name"><a href="/books/naruhounix">なるほどUnixプロセス ― Rubyで学ぶUnixの基礎</a></h3>
|
|
110
|
-
<p itemprop="author" class="author">Jesse Storimer(著), 島田 浩二(訳), 角谷 信太郎(訳)</p>
|
|
111
|
-
</article>
|
|
112
|
-
</body></html>`;
|
|
113
|
-
const http = new MockHttpClient()
|
|
114
|
-
.addResponse("https://tatsu-zine.com/books/", { status: 200, body: page1 })
|
|
115
|
-
.addResponse("https://tatsu-zine.com/books?page=2", { status: 200, body: page2 });
|
|
116
|
-
|
|
117
|
-
const results = await tatsuZineAdapter.search({ title: "なるほどUnix" }, makeDeps(http));
|
|
118
|
-
|
|
119
|
-
assert.strictEqual(results.length, 1);
|
|
120
|
-
assert.partialDeepStrictEqual(results[0], {
|
|
121
|
-
title: "なるほどUnixプロセス ― Rubyで学ぶUnixの基礎",
|
|
122
|
-
authors: ["Jesse Storimer", "島田 浩二", "角谷 信太郎"],
|
|
123
|
-
url: "https://tatsu-zine.com/books/naruhounix",
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe("getDetail()", () => {
|
|
129
|
-
it("詳細情報を返す(達人出版会は常にソーシャルDRM)", async () => {
|
|
130
|
-
const body = await loadFixture("tatsu-zine-detail-free.html");
|
|
131
|
-
const http = new MockHttpClient().addResponse(
|
|
132
|
-
"https://tatsu-zine.com/books/go-programming",
|
|
133
|
-
{ status: 200, body },
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
const book = await tatsuZineAdapter.getDetail(
|
|
137
|
-
"https://tatsu-zine.com/books/go-programming",
|
|
138
|
-
makeDeps(http),
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
assert.partialDeepStrictEqual(book, {
|
|
142
|
-
title: "Goプログラミング実践入門",
|
|
143
|
-
authors: ["Sau Sheong Chang", "武舎 広幸"],
|
|
144
|
-
publisher: "インプレス",
|
|
145
|
-
price: 3520,
|
|
146
|
-
});
|
|
147
|
-
// 達人出版会は「ソーシャルDRM」と明記がなくても全書籍で購入者情報を印字
|
|
148
|
-
assert.deepStrictEqual(book.ebookStores, [
|
|
149
|
-
{ name: "達人出版会", url: "https://tatsu-zine.com/books/go-programming", drm: "social" },
|
|
150
|
-
]);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("出版社が達人出版会自身の場合はフォールバックする", async () => {
|
|
154
|
-
const body = `<!DOCTYPE html><html><body>
|
|
155
|
-
<h1>達人出版会刊行の本</h1>
|
|
156
|
-
<img src="/images/books/999/cover.jpg">
|
|
157
|
-
<dl><dt>著者</dt><dd>著者名(著)</dd><dt>定価</dt><dd>2,200円 (2,000円+税)</dd></dl>
|
|
158
|
-
</body></html>`;
|
|
159
|
-
const http = new MockHttpClient().addResponse(
|
|
160
|
-
"https://tatsu-zine.com/books/some-book",
|
|
161
|
-
{ status: 200, body },
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
const book = await tatsuZineAdapter.getDetail(
|
|
165
|
-
"https://tatsu-zine.com/books/some-book",
|
|
166
|
-
makeDeps(http),
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
assert.strictEqual(book.publisher, "達人出版会");
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
});
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { techbookfestAdapter } from "../../../../src/adapters/publishers/techbookfest.js";
|
|
6
|
-
import { MockHttpClient } from "../../../../src/adapters/http/mock-client.js";
|
|
7
|
-
import { CheerioHtmlParser } from "../../../../src/adapters/html/cheerio-parser.js";
|
|
8
|
-
import { NullCacheStore } from "../../../../src/adapters/cache/null-cache.js";
|
|
9
|
-
|
|
10
|
-
const FIXTURES_DIR = join(import.meta.dirname, "../../../fixtures");
|
|
11
|
-
|
|
12
|
-
const XSRF_TOKEN = "test-xsrf-token-abc123";
|
|
13
|
-
|
|
14
|
-
function makeDeps(http: MockHttpClient) {
|
|
15
|
-
return { http, parser: new CheerioHtmlParser(), cache: new NullCacheStore() };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** トップページ GET → XSRF-TOKEN → GraphQL POST の 2 ステップを登録する */
|
|
19
|
-
async function makeSearchHttp(fixtureName: string): Promise<MockHttpClient> {
|
|
20
|
-
const body = await readFile(join(FIXTURES_DIR, fixtureName), "utf-8");
|
|
21
|
-
return new MockHttpClient()
|
|
22
|
-
.addResponse("https://techbookfest.org", {
|
|
23
|
-
status: 200,
|
|
24
|
-
body: "",
|
|
25
|
-
headers: { "set-cookie": `XSRF-TOKEN=${encodeURIComponent(XSRF_TOKEN)}; Path=/; Secure` },
|
|
26
|
-
})
|
|
27
|
-
.addPostResponse("https://techbookfest.org/api/graphql", { status: 200, body });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe("techbookfestAdapter", () => {
|
|
31
|
-
describe("search()", () => {
|
|
32
|
-
it("GraphQL レスポンスから BookRecord[] を返す", async () => {
|
|
33
|
-
const http = await makeSearchHttp("techbookfest-search.json");
|
|
34
|
-
|
|
35
|
-
const results = await techbookfestAdapter.search({ title: "TypeScript", limit: 10 }, makeDeps(http));
|
|
36
|
-
|
|
37
|
-
assert.strictEqual(results.length, 3);
|
|
38
|
-
assert.partialDeepStrictEqual(results[0], {
|
|
39
|
-
title: "TypeScriptで学ぶデザインパターン",
|
|
40
|
-
authors: ["サークル名A"],
|
|
41
|
-
publisher: "技術書典",
|
|
42
|
-
url: "https://techbookfest.org/product/01HXXXX1",
|
|
43
|
-
price: 1000,
|
|
44
|
-
publishedAt: "2024-01-15",
|
|
45
|
-
});
|
|
46
|
-
assert.strictEqual(results[0].coverImageUrl, "https://techbookfest.org/api/image/01HXXXX1.png");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("ebookStores に技術書典(DRMフリー)が含まれる", async () => {
|
|
50
|
-
const http = await makeSearchHttp("techbookfest-search.json");
|
|
51
|
-
|
|
52
|
-
const results = await techbookfestAdapter.search({ title: "TypeScript" }, makeDeps(http));
|
|
53
|
-
|
|
54
|
-
assert.deepStrictEqual(results[0].ebookStores, [
|
|
55
|
-
{ name: "技術書典", url: "https://techbookfest.org/product/01HXXXX1", drm: "free" },
|
|
56
|
-
]);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("coverImage が null の場合 coverImageUrl は undefined", async () => {
|
|
60
|
-
const http = await makeSearchHttp("techbookfest-search.json");
|
|
61
|
-
|
|
62
|
-
const results = await techbookfestAdapter.search({ title: "TypeScript" }, makeDeps(http));
|
|
63
|
-
|
|
64
|
-
const book = results.find(b => b.url === "https://techbookfest.org/product/01HXXXX2");
|
|
65
|
-
assert.strictEqual(book?.coverImageUrl, undefined);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("title も author も空の場合は [] を返しHTTPを呼ばない", async () => {
|
|
69
|
-
const http = new MockHttpClient();
|
|
70
|
-
const results = await techbookfestAdapter.search({}, makeDeps(http));
|
|
71
|
-
|
|
72
|
-
assert.deepStrictEqual(results, []);
|
|
73
|
-
assert.strictEqual(http.calls.length, 0);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("トップページ GET で XSRF-TOKEN を取得してから GraphQL に POST する", async () => {
|
|
77
|
-
const http = await makeSearchHttp("techbookfest-search.json");
|
|
78
|
-
|
|
79
|
-
await techbookfestAdapter.search({ title: "TypeScript" }, makeDeps(http));
|
|
80
|
-
|
|
81
|
-
assert.strictEqual(http.calls[0], "https://techbookfest.org");
|
|
82
|
-
assert.strictEqual(http.calls[1], "https://techbookfest.org/api/graphql");
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("price が 0 の書籍も返す", async () => {
|
|
86
|
-
const http = await makeSearchHttp("techbookfest-search.json");
|
|
87
|
-
|
|
88
|
-
const results = await techbookfestAdapter.search({ title: "TypeScript" }, makeDeps(http));
|
|
89
|
-
|
|
90
|
-
const freeBook = results.find(b => b.url === "https://techbookfest.org/product/01HXXXX3");
|
|
91
|
-
assert.strictEqual(freeBook?.price, 0);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { DEFAULT_PUBLISHERS } from "../../../src/adapters/publishers/registry.js";
|
|
4
|
-
|
|
5
|
-
describe("DEFAULT_PUBLISHERS", () => {
|
|
6
|
-
it("アダプターが1件以上登録されている", () => {
|
|
7
|
-
assert.ok(DEFAULT_PUBLISHERS.length > 0);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("id がすべて一意である", () => {
|
|
11
|
-
const ids = DEFAULT_PUBLISHERS.map(p => p.id);
|
|
12
|
-
const uniqueIds = new Set(ids);
|
|
13
|
-
assert.strictEqual(uniqueIds.size, ids.length);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("baseUrl がすべて一意である", () => {
|
|
17
|
-
const urls = DEFAULT_PUBLISHERS.map(p => p.baseUrl);
|
|
18
|
-
const uniqueUrls = new Set(urls);
|
|
19
|
-
assert.strictEqual(uniqueUrls.size, urls.length);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("各アダプターが必須フィールドを持つ", () => {
|
|
23
|
-
for (const p of DEFAULT_PUBLISHERS) {
|
|
24
|
-
assert.ok(p.id, `${p.id}: id が空`);
|
|
25
|
-
assert.ok(p.name, `${p.id}: name が空`);
|
|
26
|
-
assert.ok(p.baseUrl, `${p.id}: baseUrl が空`);
|
|
27
|
-
assert.strictEqual(typeof p.search, "function", `${p.id}: search が関数でない`);
|
|
28
|
-
assert.strictEqual(typeof p.getDetail, "function", `${p.id}: getDetail が関数でない`);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("baseUrl がすべて https:// で始まる", () => {
|
|
33
|
-
for (const p of DEFAULT_PUBLISHERS) {
|
|
34
|
-
assert.match(p.baseUrl, /^https:\/\//, `${p.id}: baseUrl が https でない`);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
});
|