@zonuexe/techbook-mcp 0.2.3 → 0.2.4

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/README.md +39 -20
  3. package/dist/adapters/publishers/google-books.d.ts +4 -0
  4. package/dist/adapters/publishers/google-books.d.ts.map +1 -0
  5. package/dist/adapters/publishers/google-books.js +75 -0
  6. package/dist/adapters/publishers/google-books.js.map +1 -0
  7. package/dist/adapters/publishers/isbn-publisher-codes.d.ts +21 -0
  8. package/dist/adapters/publishers/isbn-publisher-codes.d.ts.map +1 -0
  9. package/dist/adapters/publishers/isbn-publisher-codes.js +49 -0
  10. package/dist/adapters/publishers/isbn-publisher-codes.js.map +1 -0
  11. package/dist/adapters/publishers/juse-p.d.ts +3 -0
  12. package/dist/adapters/publishers/juse-p.d.ts.map +1 -0
  13. package/dist/adapters/publishers/juse-p.js +110 -0
  14. package/dist/adapters/publishers/juse-p.js.map +1 -0
  15. package/dist/adapters/publishers/registry.d.ts.map +1 -1
  16. package/dist/adapters/publishers/registry.js +4 -0
  17. package/dist/adapters/publishers/registry.js.map +1 -1
  18. package/dist/application/get-book-by-isbn.d.ts +3 -2
  19. package/dist/application/get-book-by-isbn.d.ts.map +1 -1
  20. package/dist/application/get-book-by-isbn.js +22 -3
  21. package/dist/application/get-book-by-isbn.js.map +1 -1
  22. package/dist/config/credentials.d.ts +8 -0
  23. package/dist/config/credentials.d.ts.map +1 -0
  24. package/dist/config/credentials.js +32 -0
  25. package/dist/config/credentials.js.map +1 -0
  26. package/dist/main.js +15 -1
  27. package/dist/main.js.map +1 -1
  28. package/dist/setup.d.ts +2 -0
  29. package/dist/setup.d.ts.map +1 -0
  30. package/dist/setup.js +43 -0
  31. package/dist/setup.js.map +1 -0
  32. package/flake.lock +61 -0
  33. package/package.json +1 -1
  34. package/.claude/settings.local.json +0 -38
  35. package/.codex/skills/techbook-mcp-release-prep/SKILL.md +0 -105
  36. package/.github/workflows/test.yml +0 -72
  37. package/.oxlintrc.json +0 -12
  38. package/AGENTS.md +0 -100
  39. package/deno.json +0 -3
  40. package/src/adapters/cache/memory-cache.ts +0 -31
  41. package/src/adapters/cache/null-cache.ts +0 -8
  42. package/src/adapters/calil.ts +0 -57
  43. package/src/adapters/html/cheerio-parser.ts +0 -50
  44. package/src/adapters/http/fetch-client.ts +0 -47
  45. package/src/adapters/http/mock-client.ts +0 -77
  46. package/src/adapters/openbd.ts +0 -142
  47. package/src/adapters/publishers/base.ts +0 -279
  48. package/src/adapters/publishers/book-tech.ts +0 -117
  49. package/src/adapters/publishers/born-digital.ts +0 -143
  50. package/src/adapters/publishers/coronasha.ts +0 -139
  51. package/src/adapters/publishers/gihyo.ts +0 -120
  52. package/src/adapters/publishers/impress.ts +0 -103
  53. package/src/adapters/publishers/lambdanote.ts +0 -146
  54. package/src/adapters/publishers/manatee.ts +0 -113
  55. package/src/adapters/publishers/maruzen-publishing.ts +0 -129
  56. package/src/adapters/publishers/optronics.ts +0 -113
  57. package/src/adapters/publishers/oreilly-japan.ts +0 -133
  58. package/src/adapters/publishers/peaks.ts +0 -98
  59. package/src/adapters/publishers/personal-media.ts +0 -168
  60. package/src/adapters/publishers/registry.ts +0 -38
  61. package/src/adapters/publishers/rutles.ts +0 -149
  62. package/src/adapters/publishers/saiensu.ts +0 -136
  63. package/src/adapters/publishers/seshop.ts +0 -121
  64. package/src/adapters/publishers/tatsu-zine.ts +0 -142
  65. package/src/adapters/publishers/techbookfest.ts +0 -179
  66. package/src/application/get-book-by-isbn.ts +0 -50
  67. package/src/application/get-book-detail.ts +0 -40
  68. package/src/application/search-books.ts +0 -64
  69. package/src/domain/book.ts +0 -35
  70. package/src/domain/publisher.ts +0 -18
  71. package/src/main.ts +0 -14
  72. package/src/mcp/server.ts +0 -113
  73. package/src/mcp/tools.ts +0 -71
  74. package/src/ports/cache.ts +0 -5
  75. package/src/ports/html-parser.ts +0 -15
  76. package/src/ports/http.ts +0 -17
  77. package/tests/fixtures/book-tech-detail.html +0 -51
  78. package/tests/fixtures/book-tech-search.html +0 -91
  79. package/tests/fixtures/born-digital-detail.html +0 -62
  80. package/tests/fixtures/born-digital-search.html +0 -51
  81. package/tests/fixtures/calil-book.html +0 -987
  82. package/tests/fixtures/coronasha-detail.html +0 -41
  83. package/tests/fixtures/coronasha-search.html +0 -61
  84. package/tests/fixtures/gihyo-detail.html +0 -42
  85. package/tests/fixtures/gihyo-search.json +0 -54
  86. package/tests/fixtures/impress-detail-epub.html +0 -746
  87. package/tests/fixtures/impress-detail-social.html +0 -689
  88. package/tests/fixtures/lambdanote-search.html +0 -66
  89. package/tests/fixtures/manatee-detail.html +0 -53
  90. package/tests/fixtures/manatee-search.html +0 -59
  91. package/tests/fixtures/maruzen-detail.html +0 -51
  92. package/tests/fixtures/maruzen-search.html +0 -60
  93. package/tests/fixtures/openbd-response.json +0 -110
  94. package/tests/fixtures/optronics-detail.html +0 -30
  95. package/tests/fixtures/optronics-search.html +0 -75
  96. package/tests/fixtures/oreilly-detail.html +0 -52
  97. package/tests/fixtures/oreilly-ebook-list.html +0 -53
  98. package/tests/fixtures/peaks-detail.html +0 -39
  99. package/tests/fixtures/peaks-top.html +0 -50
  100. package/tests/fixtures/personal-media-detail.html +0 -32
  101. package/tests/fixtures/personal-media-search.html +0 -39
  102. package/tests/fixtures/rutles-detail.html +0 -32
  103. package/tests/fixtures/rutles-search.html +0 -62
  104. package/tests/fixtures/saiensu-detail.html +0 -41
  105. package/tests/fixtures/saiensu-search.html +0 -65
  106. package/tests/fixtures/seshop-detail.html +0 -45
  107. package/tests/fixtures/seshop-search.html +0 -58
  108. package/tests/fixtures/tatsu-zine-detail-free.html +0 -24
  109. package/tests/fixtures/tatsu-zine-search.html +0 -40
  110. package/tests/fixtures/techbookfest-search.json +0 -73
  111. package/tests/unit/adapters/base.test.ts +0 -441
  112. package/tests/unit/adapters/calil.test.ts +0 -69
  113. package/tests/unit/adapters/openbd.test.ts +0 -185
  114. package/tests/unit/adapters/publishers/book-tech.test.ts +0 -186
  115. package/tests/unit/adapters/publishers/born-digital.test.ts +0 -194
  116. package/tests/unit/adapters/publishers/coronasha.test.ts +0 -207
  117. package/tests/unit/adapters/publishers/gihyo.test.ts +0 -137
  118. package/tests/unit/adapters/publishers/impress.test.ts +0 -129
  119. package/tests/unit/adapters/publishers/lambdanote.test.ts +0 -85
  120. package/tests/unit/adapters/publishers/manatee.test.ts +0 -165
  121. package/tests/unit/adapters/publishers/maruzen-publishing.test.ts +0 -179
  122. package/tests/unit/adapters/publishers/optronics.test.ts +0 -208
  123. package/tests/unit/adapters/publishers/oreilly-japan.test.ts +0 -194
  124. package/tests/unit/adapters/publishers/peaks.test.ts +0 -177
  125. package/tests/unit/adapters/publishers/personal-media.test.ts +0 -199
  126. package/tests/unit/adapters/publishers/rutles.test.ts +0 -173
  127. package/tests/unit/adapters/publishers/saiensu.test.ts +0 -169
  128. package/tests/unit/adapters/publishers/seshop.test.ts +0 -174
  129. package/tests/unit/adapters/publishers/tatsu-zine.test.ts +0 -172
  130. package/tests/unit/adapters/publishers/techbookfest.test.ts +0 -94
  131. package/tests/unit/adapters/registry.test.ts +0 -37
  132. package/tests/unit/application/get-book-by-isbn.test.ts +0 -176
  133. package/tests/unit/application/get-book-detail.test.ts +0 -102
  134. package/tests/unit/application/search-books.test.ts +0 -137
  135. package/tsconfig.json +0 -17
@@ -1,179 +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 { maruzenPublishingAdapter } from "../../../../src/adapters/publishers/maruzen-publishing.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("maruzenPublishingAdapter", () => {
21
- describe("search()", () => {
22
- it("BookRecord[] を返す", async () => {
23
- const body = await loadFixture("maruzen-search.html");
24
- const http = new MockHttpClient().addResponse(
25
- "https://www.maruzen-publishing.co.jp/search/",
26
- { status: 200, body },
27
- );
28
-
29
- const results = await maruzenPublishingAdapter.search({ title: "TypeScript" }, makeDeps(http));
30
-
31
- assert.ok(results.length >= 1);
32
- assert.partialDeepStrictEqual(results[0], {
33
- title: "プログラミングTypeScript",
34
- publisher: "丸善出版",
35
- url: "https://www.maruzen-publishing.co.jp/book/b10152370.html",
36
- });
37
- });
38
-
39
- it("著者の役割語を除去する", async () => {
40
- const body = await loadFixture("maruzen-search.html");
41
- const http = new MockHttpClient().addResponse(
42
- "https://www.maruzen-publishing.co.jp/search/",
43
- { status: 200, body },
44
- );
45
-
46
- const results = await maruzenPublishingAdapter.search({ title: "TypeScript" }, makeDeps(http));
47
-
48
- assert.deepStrictEqual(results[0].authors, ["ボリス・チェルニー", "折山文哉"]);
49
- });
50
-
51
- it("coverImageUrl が設定される", async () => {
52
- const body = await loadFixture("maruzen-search.html");
53
- const http = new MockHttpClient().addResponse(
54
- "https://www.maruzen-publishing.co.jp/search/",
55
- { status: 200, body },
56
- );
57
-
58
- const results = await maruzenPublishingAdapter.search({ title: "TypeScript" }, makeDeps(http));
59
-
60
- assert.strictEqual(
61
- results[0].coverImageUrl,
62
- "https://www.maruzen-publishing.co.jp/files/isbn/978-4-621-30855-1.jpg",
63
- );
64
- });
65
-
66
- it("title も author も空の場合は [] を返しHTTPを呼ばない", async () => {
67
- const http = new MockHttpClient();
68
-
69
- const results = await maruzenPublishingAdapter.search({}, makeDeps(http));
70
-
71
- assert.deepStrictEqual(results, []);
72
- assert.strictEqual(http.calls.length, 0);
73
- });
74
-
75
- it("検索リクエストに search_keyword が含まれる", async () => {
76
- const body = await loadFixture("maruzen-search.html");
77
- const http = new MockHttpClient().addResponse(
78
- "https://www.maruzen-publishing.co.jp/search/",
79
- { status: 200, body },
80
- );
81
-
82
- await maruzenPublishingAdapter.search({ title: "TypeScript" }, makeDeps(http));
83
-
84
- assert.ok(http.calls[0].includes("search_keyword=TypeScript"));
85
- });
86
-
87
- it("検索リクエストに format=1 が含まれる", async () => {
88
- const body = await loadFixture("maruzen-search.html");
89
- const http = new MockHttpClient().addResponse(
90
- "https://www.maruzen-publishing.co.jp/search/",
91
- { status: 200, body },
92
- );
93
-
94
- await maruzenPublishingAdapter.search({ title: "TypeScript" }, makeDeps(http));
95
-
96
- assert.ok(http.calls[0].includes("format=1"));
97
- });
98
-
99
- it("3件取得できる", async () => {
100
- const body = await loadFixture("maruzen-search.html");
101
- const http = new MockHttpClient().addResponse(
102
- "https://www.maruzen-publishing.co.jp/search/",
103
- { status: 200, body },
104
- );
105
-
106
- const results = await maruzenPublishingAdapter.search({ title: "プログラム" }, makeDeps(http));
107
-
108
- assert.strictEqual(results.length, 3);
109
- });
110
-
111
- it("監訳者の役割語も除去する", async () => {
112
- const body = await loadFixture("maruzen-search.html");
113
- const http = new MockHttpClient().addResponse(
114
- "https://www.maruzen-publishing.co.jp/search/",
115
- { status: 200, body },
116
- );
117
-
118
- const results = await maruzenPublishingAdapter.search({ title: "統計" }, makeDeps(http));
119
- const statsBook = results.find(r => r.title.includes("統計"));
120
-
121
- assert.deepStrictEqual(statsBook?.authors, ["ピーター・ブルース", "アンドリュー・ブルース", "大橋真也"]);
122
- });
123
- });
124
-
125
- describe("getDetail()", () => {
126
- it("詳細情報を返す", async () => {
127
- const body = await loadFixture("maruzen-detail.html");
128
- const http = new MockHttpClient().addResponse(
129
- "https://www.maruzen-publishing.co.jp/book/b10152370.html",
130
- { status: 200, body },
131
- );
132
-
133
- const book = await maruzenPublishingAdapter.getDetail(
134
- "https://www.maruzen-publishing.co.jp/book/b10152370.html",
135
- makeDeps(http),
136
- );
137
-
138
- assert.partialDeepStrictEqual(book, {
139
- title: "プログラミングTypeScript",
140
- publisher: "丸善出版",
141
- publishedAt: "2020-03-31",
142
- });
143
- });
144
-
145
- it("著者の役割語を除去する", async () => {
146
- const body = await loadFixture("maruzen-detail.html");
147
- const http = new MockHttpClient().addResponse(
148
- "https://www.maruzen-publishing.co.jp/book/b10152370.html",
149
- { status: 200, body },
150
- );
151
-
152
- const book = await maruzenPublishingAdapter.getDetail(
153
- "https://www.maruzen-publishing.co.jp/book/b10152370.html",
154
- makeDeps(http),
155
- );
156
-
157
- assert.deepStrictEqual(book.authors, ["ボリス・チェルニー", "折山文哉"]);
158
- });
159
-
160
- it("ebookStores に Kindle と Kinoppy と honto が含まれ Knowledge Worker は除外される", async () => {
161
- const body = await loadFixture("maruzen-detail.html");
162
- const http = new MockHttpClient().addResponse(
163
- "https://www.maruzen-publishing.co.jp/book/b10152370.html",
164
- { status: 200, body },
165
- );
166
-
167
- const book = await maruzenPublishingAdapter.getDetail(
168
- "https://www.maruzen-publishing.co.jp/book/b10152370.html",
169
- makeDeps(http),
170
- );
171
-
172
- const storeNames = book.ebookStores?.map(s => s.name) ?? [];
173
- assert.ok(storeNames.includes("Kindle"));
174
- assert.ok(storeNames.includes("Kinoppy"));
175
- assert.ok(storeNames.includes("honto"));
176
- assert.ok(!storeNames.includes("Knowledge Worker"));
177
- });
178
- });
179
- });
@@ -1,208 +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 { optronicsAdapter } from "../../../../src/adapters/publishers/optronics.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("optronicsAdapter", () => {
21
- describe("search()", () => {
22
- it("BookRecord[] を返す", async () => {
23
- const body = await loadFixture("optronics-search.html");
24
- const http = new MockHttpClient().addResponse(
25
- "https://optronics-ebook.com/products/list.php",
26
- { status: 200, body },
27
- );
28
-
29
- const results = await optronicsAdapter.search({ title: "センシング" }, makeDeps(http));
30
-
31
- assert.strictEqual(results.length, 2);
32
- assert.partialDeepStrictEqual(results[0], {
33
- title: "感性計測&感覚センサ技術集成",
34
- url: "https://optronics-ebook.com/products/detail.php?product_id=235",
35
- price: 15000,
36
- });
37
- });
38
-
39
- it("listcomment から発行元を取得する", async () => {
40
- const body = await loadFixture("optronics-search.html");
41
- const http = new MockHttpClient().addResponse(
42
- "https://optronics-ebook.com/products/list.php",
43
- { status: 200, body },
44
- );
45
-
46
- const results = await optronicsAdapter.search({ title: "センシング" }, makeDeps(http));
47
-
48
- assert.strictEqual(results[0].publisher, "センシンディー株式会社");
49
- });
50
-
51
- it("listcomment から著者を取得する", async () => {
52
- const body = await loadFixture("optronics-search.html");
53
- const http = new MockHttpClient().addResponse(
54
- "https://optronics-ebook.com/products/list.php",
55
- { status: 200, body },
56
- );
57
-
58
- const results = await optronicsAdapter.search({ title: "センシング" }, makeDeps(http));
59
-
60
- // 2件目は著者フィールドあり
61
- assert.deepStrictEqual(results[1].authors, ["波多腰 玄一"]);
62
- });
63
-
64
- it("coverImageUrl が設定される", async () => {
65
- const body = await loadFixture("optronics-search.html");
66
- const http = new MockHttpClient().addResponse(
67
- "https://optronics-ebook.com/products/list.php",
68
- { status: 200, body },
69
- );
70
-
71
- const results = await optronicsAdapter.search({ title: "センシング" }, makeDeps(http));
72
-
73
- assert.strictEqual(
74
- results[0].coverImageUrl,
75
- "https://optronics-ebook.com/upload/save_image/03031025_69a6388916874.jpg",
76
- );
77
- });
78
-
79
- it("ebookStores にオプトロニクス社 (DRMフリー) が含まれる", async () => {
80
- const body = await loadFixture("optronics-search.html");
81
- const http = new MockHttpClient().addResponse(
82
- "https://optronics-ebook.com/products/list.php",
83
- { status: 200, body },
84
- );
85
-
86
- const results = await optronicsAdapter.search({ title: "センシング" }, makeDeps(http));
87
-
88
- assert.deepStrictEqual(results[0].ebookStores, [
89
- {
90
- name: "オプトロニクス社",
91
- url: "https://optronics-ebook.com/products/detail.php?product_id=235",
92
- drm: "free",
93
- },
94
- ]);
95
- });
96
-
97
- it("title も author も空の場合は [] を返しHTTPを呼ばない", async () => {
98
- const http = new MockHttpClient();
99
-
100
- const results = await optronicsAdapter.search({}, makeDeps(http));
101
-
102
- assert.deepStrictEqual(results, []);
103
- assert.strictEqual(http.calls.length, 0);
104
- });
105
-
106
- it("検索URLに name と category_id=1 が含まれる", async () => {
107
- const body = await loadFixture("optronics-search.html");
108
- const http = new MockHttpClient().addResponse(
109
- "https://optronics-ebook.com/products/list.php",
110
- { status: 200, body },
111
- );
112
-
113
- await optronicsAdapter.search({ title: "センシング" }, makeDeps(http));
114
-
115
- assert.ok(http.calls[0].includes("name=%E3%82%BB%E3%83%B3%E3%82%B7%E3%83%B3%E3%82%B0"));
116
- assert.ok(http.calls[0].includes("category_id=1"));
117
- });
118
- });
119
-
120
- describe("getDetail()", () => {
121
- it("詳細情報を返す", async () => {
122
- const body = await loadFixture("optronics-detail.html");
123
- const http = new MockHttpClient().addResponse(
124
- "https://optronics-ebook.com/products/detail.php?product_id=210",
125
- { status: 200, body },
126
- );
127
-
128
- const book = await optronicsAdapter.getDetail(
129
- "https://optronics-ebook.com/products/detail.php?product_id=210",
130
- makeDeps(http),
131
- );
132
-
133
- assert.partialDeepStrictEqual(book, {
134
- title: "光センシング技術の最前線",
135
- price: 20000,
136
- });
137
- });
138
-
139
- it("main_comment から著者を取得する", async () => {
140
- const body = await loadFixture("optronics-detail.html");
141
- const http = new MockHttpClient().addResponse(
142
- "https://optronics-ebook.com/products/detail.php?product_id=210",
143
- { status: 200, body },
144
- );
145
-
146
- const book = await optronicsAdapter.getDetail(
147
- "https://optronics-ebook.com/products/detail.php?product_id=210",
148
- makeDeps(http),
149
- );
150
-
151
- assert.deepStrictEqual(book.authors, ["波多腰 玄一"]);
152
- });
153
-
154
- it("main_comment から発行元を取得し ㈱ を除去する", async () => {
155
- const body = await loadFixture("optronics-detail.html");
156
- const http = new MockHttpClient().addResponse(
157
- "https://optronics-ebook.com/products/detail.php?product_id=210",
158
- { status: 200, body },
159
- );
160
-
161
- const book = await optronicsAdapter.getDetail(
162
- "https://optronics-ebook.com/products/detail.php?product_id=210",
163
- makeDeps(http),
164
- );
165
-
166
- assert.strictEqual(book.publisher, "オプトロニクス社");
167
- });
168
-
169
- it("coverImageUrl が設定される", async () => {
170
- const body = await loadFixture("optronics-detail.html");
171
- const http = new MockHttpClient().addResponse(
172
- "https://optronics-ebook.com/products/detail.php?product_id=210",
173
- { status: 200, body },
174
- );
175
-
176
- const book = await optronicsAdapter.getDetail(
177
- "https://optronics-ebook.com/products/detail.php?product_id=210",
178
- makeDeps(http),
179
- );
180
-
181
- assert.strictEqual(
182
- book.coverImageUrl,
183
- "https://optronics-ebook.com/upload/save_image/11141121_636a1e0f7bd39.jpg",
184
- );
185
- });
186
-
187
- it("ebookStores にオプトロニクス社 (DRMフリー) が含まれる", async () => {
188
- const body = await loadFixture("optronics-detail.html");
189
- const http = new MockHttpClient().addResponse(
190
- "https://optronics-ebook.com/products/detail.php?product_id=210",
191
- { status: 200, body },
192
- );
193
-
194
- const book = await optronicsAdapter.getDetail(
195
- "https://optronics-ebook.com/products/detail.php?product_id=210",
196
- makeDeps(http),
197
- );
198
-
199
- assert.deepStrictEqual(book.ebookStores, [
200
- {
201
- name: "オプトロニクス社",
202
- url: "https://optronics-ebook.com/products/detail.php?product_id=210",
203
- drm: "free",
204
- },
205
- ]);
206
- });
207
- });
208
- });
@@ -1,194 +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 { oreillyJapanAdapter } from "../../../../src/adapters/publishers/oreilly-japan.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("oreillyJapanAdapter", () => {
21
- describe("search()", () => {
22
- it("タイトルでフィルタリングして BookRecord[] を返す", async () => {
23
- const body = await loadFixture("oreilly-ebook-list.html");
24
- const http = new MockHttpClient().addResponse(
25
- "https://www.oreilly.co.jp/ebook/",
26
- { status: 200, body },
27
- );
28
-
29
- const results = await oreillyJapanAdapter.search({ title: "TypeScript" }, makeDeps(http));
30
-
31
- assert.strictEqual(results.length, 1);
32
- assert.partialDeepStrictEqual(results[0], {
33
- title: "Effective TypeScript 第2版",
34
- publisher: "オライリー・ジャパン",
35
- url: "https://www.oreilly.co.jp/books/9784814401093/",
36
- isbn: "9784814401093",
37
- price: 4620,
38
- publishedAt: "2025-04-08",
39
- });
40
- });
41
-
42
- it("ebookStores にオライリー・ジャパン(DRMフリー)が含まれる", async () => {
43
- const body = await loadFixture("oreilly-ebook-list.html");
44
- const http = new MockHttpClient().addResponse(
45
- "https://www.oreilly.co.jp/ebook/",
46
- { status: 200, body },
47
- );
48
-
49
- const results = await oreillyJapanAdapter.search({ title: "TypeScript" }, makeDeps(http));
50
-
51
- assert.deepStrictEqual(results[0].ebookStores, [
52
- {
53
- name: "オライリー・ジャパン",
54
- url: "https://www.oreilly.co.jp/books/9784814401093/",
55
- drm: "free",
56
- },
57
- ]);
58
- });
59
-
60
- it("coverImageUrl が ISBN から構築される", async () => {
61
- const body = await loadFixture("oreilly-ebook-list.html");
62
- const http = new MockHttpClient().addResponse(
63
- "https://www.oreilly.co.jp/ebook/",
64
- { status: 200, body },
65
- );
66
-
67
- const results = await oreillyJapanAdapter.search({ title: "TypeScript" }, makeDeps(http));
68
-
69
- assert.strictEqual(
70
- results[0].coverImageUrl,
71
- "https://www.oreilly.co.jp/books/images/picture_large978-4-8144-0109-3.jpeg",
72
- );
73
- });
74
-
75
- it("大文字小文字を区別せずにフィルタリングする", async () => {
76
- const body = await loadFixture("oreilly-ebook-list.html");
77
- const http = new MockHttpClient().addResponse(
78
- "https://www.oreilly.co.jp/ebook/",
79
- { status: 200, body },
80
- );
81
-
82
- const results = await oreillyJapanAdapter.search({ title: "typescript" }, makeDeps(http));
83
-
84
- assert.strictEqual(results.length, 1);
85
- });
86
-
87
- it("limit を適用する", async () => {
88
- const body = await loadFixture("oreilly-ebook-list.html");
89
- const http = new MockHttpClient().addResponse(
90
- "https://www.oreilly.co.jp/ebook/",
91
- { status: 200, body },
92
- );
93
-
94
- const results = await oreillyJapanAdapter.search({ title: "の", limit: 1 }, makeDeps(http));
95
-
96
- assert.strictEqual(results.length, 1);
97
- });
98
-
99
- it("title が未指定の場合は [] を返しHTTPを呼ばない", async () => {
100
- const http = new MockHttpClient();
101
-
102
- const results = await oreillyJapanAdapter.search({}, makeDeps(http));
103
-
104
- assert.deepStrictEqual(results, []);
105
- assert.strictEqual(http.calls.length, 0);
106
- });
107
-
108
- it("author のみの場合も [] を返しHTTPを呼ばない", async () => {
109
- const http = new MockHttpClient();
110
-
111
- const results = await oreillyJapanAdapter.search({ author: "Dan Vanderkam" }, makeDeps(http));
112
-
113
- assert.deepStrictEqual(results, []);
114
- assert.strictEqual(http.calls.length, 0);
115
- });
116
- });
117
-
118
- describe("getDetail()", () => {
119
- it("詳細情報を返す", async () => {
120
- const body = await loadFixture("oreilly-detail.html");
121
- const http = new MockHttpClient().addResponse(
122
- "https://www.oreilly.co.jp/books/9784814401093/",
123
- { status: 200, body },
124
- );
125
-
126
- const book = await oreillyJapanAdapter.getDetail(
127
- "https://www.oreilly.co.jp/books/9784814401093/",
128
- makeDeps(http),
129
- );
130
-
131
- assert.partialDeepStrictEqual(book, {
132
- isbn: "9784814401093",
133
- price: 4620,
134
- publishedAt: "2025-04-08",
135
- publisher: "オライリー・ジャパン",
136
- });
137
- assert.ok(book.title.includes("Effective TypeScript 第2版"));
138
- });
139
-
140
- it("著者が配列で返される(役割語を除去)", async () => {
141
- const body = await loadFixture("oreilly-detail.html");
142
- const http = new MockHttpClient().addResponse(
143
- "https://www.oreilly.co.jp/books/9784814401093/",
144
- { status: 200, body },
145
- );
146
-
147
- const book = await oreillyJapanAdapter.getDetail(
148
- "https://www.oreilly.co.jp/books/9784814401093/",
149
- makeDeps(http),
150
- );
151
-
152
- assert.deepStrictEqual(book.authors, ["Dan Vanderkam", "今村 謙士"]);
153
- });
154
-
155
- it("coverImageUrl が取得される", async () => {
156
- const body = await loadFixture("oreilly-detail.html");
157
- const http = new MockHttpClient().addResponse(
158
- "https://www.oreilly.co.jp/books/9784814401093/",
159
- { status: 200, body },
160
- );
161
-
162
- const book = await oreillyJapanAdapter.getDetail(
163
- "https://www.oreilly.co.jp/books/9784814401093/",
164
- makeDeps(http),
165
- );
166
-
167
- assert.strictEqual(
168
- book.coverImageUrl,
169
- "https://www.oreilly.co.jp/books/images/picture_large978-4-8144-0109-3.jpeg",
170
- );
171
- });
172
-
173
- it("ebookStores にオライリー・ジャパン(DRMフリー)が含まれる", async () => {
174
- const body = await loadFixture("oreilly-detail.html");
175
- const http = new MockHttpClient().addResponse(
176
- "https://www.oreilly.co.jp/books/9784814401093/",
177
- { status: 200, body },
178
- );
179
-
180
- const book = await oreillyJapanAdapter.getDetail(
181
- "https://www.oreilly.co.jp/books/9784814401093/",
182
- makeDeps(http),
183
- );
184
-
185
- assert.deepStrictEqual(book.ebookStores, [
186
- {
187
- name: "オライリー・ジャパン",
188
- url: "https://www.oreilly.co.jp/books/9784814401093/",
189
- drm: "free",
190
- },
191
- ]);
192
- });
193
- });
194
- });