@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.
Files changed (207) hide show
  1. package/CHANGELOG.md +52 -1
  2. package/README.md +54 -22
  3. package/dist/adapters/http/fetch-client.d.ts.map +1 -1
  4. package/dist/adapters/http/fetch-client.js +18 -1
  5. package/dist/adapters/http/fetch-client.js.map +1 -1
  6. package/dist/adapters/openbd.d.ts.map +1 -1
  7. package/dist/adapters/openbd.js +18 -5
  8. package/dist/adapters/openbd.js.map +1 -1
  9. package/dist/adapters/publishers/base.d.ts +2 -1
  10. package/dist/adapters/publishers/base.d.ts.map +1 -1
  11. package/dist/adapters/publishers/base.js +8 -3
  12. package/dist/adapters/publishers/base.js.map +1 -1
  13. package/dist/adapters/publishers/cq-publishing.d.ts +3 -0
  14. package/dist/adapters/publishers/cq-publishing.d.ts.map +1 -0
  15. package/dist/adapters/publishers/cq-publishing.js +120 -0
  16. package/dist/adapters/publishers/cq-publishing.js.map +1 -0
  17. package/dist/adapters/publishers/google-books.d.ts +4 -0
  18. package/dist/adapters/publishers/google-books.d.ts.map +1 -0
  19. package/dist/adapters/publishers/google-books.js +76 -0
  20. package/dist/adapters/publishers/google-books.js.map +1 -0
  21. package/dist/adapters/publishers/isbn-publisher-codes.d.ts +21 -0
  22. package/dist/adapters/publishers/isbn-publisher-codes.d.ts.map +1 -0
  23. package/dist/adapters/publishers/isbn-publisher-codes.js +49 -0
  24. package/dist/adapters/publishers/isbn-publisher-codes.js.map +1 -0
  25. package/dist/adapters/publishers/juse-p.d.ts +3 -0
  26. package/dist/adapters/publishers/juse-p.d.ts.map +1 -0
  27. package/dist/adapters/publishers/juse-p.js +110 -0
  28. package/dist/adapters/publishers/juse-p.js.map +1 -0
  29. package/dist/adapters/publishers/leanpub.d.ts +3 -0
  30. package/dist/adapters/publishers/leanpub.d.ts.map +1 -0
  31. package/dist/adapters/publishers/leanpub.js +96 -0
  32. package/dist/adapters/publishers/leanpub.js.map +1 -0
  33. package/dist/adapters/publishers/oreilly-japan.d.ts.map +1 -1
  34. package/dist/adapters/publishers/oreilly-japan.js +8 -2
  35. package/dist/adapters/publishers/oreilly-japan.js.map +1 -1
  36. package/dist/adapters/publishers/peaks.d.ts.map +1 -1
  37. package/dist/adapters/publishers/peaks.js +3 -2
  38. package/dist/adapters/publishers/peaks.js.map +1 -1
  39. package/dist/adapters/publishers/personal-media.d.ts.map +1 -1
  40. package/dist/adapters/publishers/personal-media.js +3 -2
  41. package/dist/adapters/publishers/personal-media.js.map +1 -1
  42. package/dist/adapters/publishers/pragprog.d.ts +3 -0
  43. package/dist/adapters/publishers/pragprog.d.ts.map +1 -0
  44. package/dist/adapters/publishers/pragprog.js +120 -0
  45. package/dist/adapters/publishers/pragprog.js.map +1 -0
  46. package/dist/adapters/publishers/registry.d.ts.map +1 -1
  47. package/dist/adapters/publishers/registry.js +10 -0
  48. package/dist/adapters/publishers/registry.js.map +1 -1
  49. package/dist/adapters/publishers/techbookfest.d.ts.map +1 -1
  50. package/dist/adapters/publishers/techbookfest.js +2 -1
  51. package/dist/adapters/publishers/techbookfest.js.map +1 -1
  52. package/dist/application/concurrency.d.ts +16 -0
  53. package/dist/application/concurrency.d.ts.map +1 -0
  54. package/dist/application/concurrency.js +42 -0
  55. package/dist/application/concurrency.js.map +1 -0
  56. package/dist/application/get-book-by-isbn.d.ts +0 -8
  57. package/dist/application/get-book-by-isbn.d.ts.map +1 -1
  58. package/dist/application/get-book-by-isbn.js +64 -7
  59. package/dist/application/get-book-by-isbn.js.map +1 -1
  60. package/dist/application/get-book-detail.d.ts.map +1 -1
  61. package/dist/application/get-book-detail.js +3 -0
  62. package/dist/application/get-book-detail.js.map +1 -1
  63. package/dist/application/search-books.d.ts +16 -5
  64. package/dist/application/search-books.d.ts.map +1 -1
  65. package/dist/application/search-books.js +46 -9
  66. package/dist/application/search-books.js.map +1 -1
  67. package/dist/config/credentials.d.ts +8 -0
  68. package/dist/config/credentials.d.ts.map +1 -0
  69. package/dist/config/credentials.js +32 -0
  70. package/dist/config/credentials.js.map +1 -0
  71. package/dist/domain/authors.d.ts +7 -0
  72. package/dist/domain/authors.d.ts.map +1 -0
  73. package/dist/domain/authors.js +22 -0
  74. package/dist/domain/authors.js.map +1 -0
  75. package/dist/domain/book.d.ts +2 -0
  76. package/dist/domain/book.d.ts.map +1 -1
  77. package/dist/domain/isbn.d.ts +8 -0
  78. package/dist/domain/isbn.d.ts.map +1 -0
  79. package/dist/domain/isbn.js +16 -0
  80. package/dist/domain/isbn.js.map +1 -0
  81. package/dist/domain/publisher.d.ts +16 -0
  82. package/dist/domain/publisher.d.ts.map +1 -1
  83. package/dist/domain/text-match.d.ts +32 -0
  84. package/dist/domain/text-match.d.ts.map +1 -0
  85. package/dist/domain/text-match.js +84 -0
  86. package/dist/domain/text-match.js.map +1 -0
  87. package/dist/main.js +15 -1
  88. package/dist/main.js.map +1 -1
  89. package/dist/mcp/server.d.ts +6 -0
  90. package/dist/mcp/server.d.ts.map +1 -1
  91. package/dist/mcp/server.js +40 -4
  92. package/dist/mcp/server.js.map +1 -1
  93. package/dist/mcp/tools.d.ts.map +1 -1
  94. package/dist/mcp/tools.js +9 -1
  95. package/dist/mcp/tools.js.map +1 -1
  96. package/dist/setup.d.ts +2 -0
  97. package/dist/setup.d.ts.map +1 -0
  98. package/dist/setup.js +43 -0
  99. package/dist/setup.js.map +1 -0
  100. package/dist/version.d.ts +9 -0
  101. package/dist/version.d.ts.map +1 -0
  102. package/dist/version.js +9 -0
  103. package/dist/version.js.map +1 -0
  104. package/docs/design-doc.md +127 -7
  105. package/package.json +14 -15
  106. package/.claude/settings.local.json +0 -38
  107. package/.codex/skills/techbook-mcp-release-prep/SKILL.md +0 -105
  108. package/.github/workflows/test.yml +0 -72
  109. package/.oxlintrc.json +0 -12
  110. package/AGENTS.md +0 -100
  111. package/deno.json +0 -3
  112. package/src/adapters/cache/memory-cache.ts +0 -31
  113. package/src/adapters/cache/null-cache.ts +0 -8
  114. package/src/adapters/calil.ts +0 -57
  115. package/src/adapters/html/cheerio-parser.ts +0 -50
  116. package/src/adapters/http/fetch-client.ts +0 -47
  117. package/src/adapters/http/mock-client.ts +0 -77
  118. package/src/adapters/openbd.ts +0 -142
  119. package/src/adapters/publishers/base.ts +0 -279
  120. package/src/adapters/publishers/book-tech.ts +0 -117
  121. package/src/adapters/publishers/born-digital.ts +0 -143
  122. package/src/adapters/publishers/coronasha.ts +0 -139
  123. package/src/adapters/publishers/gihyo.ts +0 -120
  124. package/src/adapters/publishers/impress.ts +0 -103
  125. package/src/adapters/publishers/lambdanote.ts +0 -146
  126. package/src/adapters/publishers/manatee.ts +0 -113
  127. package/src/adapters/publishers/maruzen-publishing.ts +0 -129
  128. package/src/adapters/publishers/optronics.ts +0 -113
  129. package/src/adapters/publishers/oreilly-japan.ts +0 -133
  130. package/src/adapters/publishers/peaks.ts +0 -98
  131. package/src/adapters/publishers/personal-media.ts +0 -168
  132. package/src/adapters/publishers/registry.ts +0 -38
  133. package/src/adapters/publishers/rutles.ts +0 -149
  134. package/src/adapters/publishers/saiensu.ts +0 -136
  135. package/src/adapters/publishers/seshop.ts +0 -121
  136. package/src/adapters/publishers/tatsu-zine.ts +0 -142
  137. package/src/adapters/publishers/techbookfest.ts +0 -179
  138. package/src/application/get-book-by-isbn.ts +0 -50
  139. package/src/application/get-book-detail.ts +0 -40
  140. package/src/application/search-books.ts +0 -64
  141. package/src/domain/book.ts +0 -35
  142. package/src/domain/publisher.ts +0 -18
  143. package/src/main.ts +0 -14
  144. package/src/mcp/server.ts +0 -113
  145. package/src/mcp/tools.ts +0 -71
  146. package/src/ports/cache.ts +0 -5
  147. package/src/ports/html-parser.ts +0 -15
  148. package/src/ports/http.ts +0 -17
  149. package/tests/fixtures/book-tech-detail.html +0 -51
  150. package/tests/fixtures/book-tech-search.html +0 -91
  151. package/tests/fixtures/born-digital-detail.html +0 -62
  152. package/tests/fixtures/born-digital-search.html +0 -51
  153. package/tests/fixtures/calil-book.html +0 -987
  154. package/tests/fixtures/coronasha-detail.html +0 -41
  155. package/tests/fixtures/coronasha-search.html +0 -61
  156. package/tests/fixtures/gihyo-detail.html +0 -42
  157. package/tests/fixtures/gihyo-search.json +0 -54
  158. package/tests/fixtures/impress-detail-epub.html +0 -746
  159. package/tests/fixtures/impress-detail-social.html +0 -689
  160. package/tests/fixtures/lambdanote-search.html +0 -66
  161. package/tests/fixtures/manatee-detail.html +0 -53
  162. package/tests/fixtures/manatee-search.html +0 -59
  163. package/tests/fixtures/maruzen-detail.html +0 -51
  164. package/tests/fixtures/maruzen-search.html +0 -60
  165. package/tests/fixtures/openbd-response.json +0 -110
  166. package/tests/fixtures/optronics-detail.html +0 -30
  167. package/tests/fixtures/optronics-search.html +0 -75
  168. package/tests/fixtures/oreilly-detail.html +0 -52
  169. package/tests/fixtures/oreilly-ebook-list.html +0 -53
  170. package/tests/fixtures/peaks-detail.html +0 -39
  171. package/tests/fixtures/peaks-top.html +0 -50
  172. package/tests/fixtures/personal-media-detail.html +0 -32
  173. package/tests/fixtures/personal-media-search.html +0 -39
  174. package/tests/fixtures/rutles-detail.html +0 -32
  175. package/tests/fixtures/rutles-search.html +0 -62
  176. package/tests/fixtures/saiensu-detail.html +0 -41
  177. package/tests/fixtures/saiensu-search.html +0 -65
  178. package/tests/fixtures/seshop-detail.html +0 -45
  179. package/tests/fixtures/seshop-search.html +0 -58
  180. package/tests/fixtures/tatsu-zine-detail-free.html +0 -24
  181. package/tests/fixtures/tatsu-zine-search.html +0 -40
  182. package/tests/fixtures/techbookfest-search.json +0 -73
  183. package/tests/unit/adapters/base.test.ts +0 -441
  184. package/tests/unit/adapters/calil.test.ts +0 -69
  185. package/tests/unit/adapters/openbd.test.ts +0 -185
  186. package/tests/unit/adapters/publishers/book-tech.test.ts +0 -186
  187. package/tests/unit/adapters/publishers/born-digital.test.ts +0 -194
  188. package/tests/unit/adapters/publishers/coronasha.test.ts +0 -207
  189. package/tests/unit/adapters/publishers/gihyo.test.ts +0 -137
  190. package/tests/unit/adapters/publishers/impress.test.ts +0 -129
  191. package/tests/unit/adapters/publishers/lambdanote.test.ts +0 -85
  192. package/tests/unit/adapters/publishers/manatee.test.ts +0 -165
  193. package/tests/unit/adapters/publishers/maruzen-publishing.test.ts +0 -179
  194. package/tests/unit/adapters/publishers/optronics.test.ts +0 -208
  195. package/tests/unit/adapters/publishers/oreilly-japan.test.ts +0 -194
  196. package/tests/unit/adapters/publishers/peaks.test.ts +0 -177
  197. package/tests/unit/adapters/publishers/personal-media.test.ts +0 -199
  198. package/tests/unit/adapters/publishers/rutles.test.ts +0 -173
  199. package/tests/unit/adapters/publishers/saiensu.test.ts +0 -169
  200. package/tests/unit/adapters/publishers/seshop.test.ts +0 -174
  201. package/tests/unit/adapters/publishers/tatsu-zine.test.ts +0 -172
  202. package/tests/unit/adapters/publishers/techbookfest.test.ts +0 -94
  203. package/tests/unit/adapters/registry.test.ts +0 -37
  204. package/tests/unit/application/get-book-by-isbn.test.ts +0 -176
  205. package/tests/unit/application/get-book-detail.test.ts +0 -102
  206. package/tests/unit/application/search-books.test.ts +0 -137
  207. 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
- });