@zonuexe/techbook-mcp 0.2.2 → 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 (151) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/README.md +39 -20
  3. package/dist/adapters/calil.d.ts +10 -0
  4. package/dist/adapters/calil.d.ts.map +1 -0
  5. package/dist/adapters/calil.js +45 -0
  6. package/dist/adapters/calil.js.map +1 -0
  7. package/dist/adapters/openbd.d.ts +57 -0
  8. package/dist/adapters/openbd.d.ts.map +1 -0
  9. package/dist/adapters/openbd.js +87 -0
  10. package/dist/adapters/openbd.js.map +1 -0
  11. package/dist/adapters/publishers/google-books.d.ts +4 -0
  12. package/dist/adapters/publishers/google-books.d.ts.map +1 -0
  13. package/dist/adapters/publishers/google-books.js +75 -0
  14. package/dist/adapters/publishers/google-books.js.map +1 -0
  15. package/dist/adapters/publishers/isbn-publisher-codes.d.ts +21 -0
  16. package/dist/adapters/publishers/isbn-publisher-codes.d.ts.map +1 -0
  17. package/dist/adapters/publishers/isbn-publisher-codes.js +49 -0
  18. package/dist/adapters/publishers/isbn-publisher-codes.js.map +1 -0
  19. package/dist/adapters/publishers/juse-p.d.ts +3 -0
  20. package/dist/adapters/publishers/juse-p.d.ts.map +1 -0
  21. package/dist/adapters/publishers/juse-p.js +110 -0
  22. package/dist/adapters/publishers/juse-p.js.map +1 -0
  23. package/dist/adapters/publishers/registry.d.ts.map +1 -1
  24. package/dist/adapters/publishers/registry.js +4 -0
  25. package/dist/adapters/publishers/registry.js.map +1 -1
  26. package/dist/adapters/publishers/tatsu-zine.d.ts.map +1 -1
  27. package/dist/adapters/publishers/tatsu-zine.js +6 -18
  28. package/dist/adapters/publishers/tatsu-zine.js.map +1 -1
  29. package/dist/application/get-book-by-isbn.d.ts +13 -0
  30. package/dist/application/get-book-by-isbn.d.ts.map +1 -0
  31. package/dist/application/get-book-by-isbn.js +61 -0
  32. package/dist/application/get-book-by-isbn.js.map +1 -0
  33. package/dist/application/get-book-detail.d.ts.map +1 -1
  34. package/dist/application/get-book-detail.js +16 -1
  35. package/dist/application/get-book-detail.js.map +1 -1
  36. package/dist/application/search-books.d.ts.map +1 -1
  37. package/dist/application/search-books.js +20 -0
  38. package/dist/application/search-books.js.map +1 -1
  39. package/dist/config/credentials.d.ts +8 -0
  40. package/dist/config/credentials.d.ts.map +1 -0
  41. package/dist/config/credentials.js +32 -0
  42. package/dist/config/credentials.js.map +1 -0
  43. package/dist/main.js +15 -1
  44. package/dist/main.js.map +1 -1
  45. package/dist/mcp/server.d.ts.map +1 -1
  46. package/dist/mcp/server.js +10 -0
  47. package/dist/mcp/server.js.map +1 -1
  48. package/dist/mcp/tools.d.ts +13 -0
  49. package/dist/mcp/tools.d.ts.map +1 -1
  50. package/dist/mcp/tools.js +16 -0
  51. package/dist/mcp/tools.js.map +1 -1
  52. package/dist/setup.d.ts +2 -0
  53. package/dist/setup.d.ts.map +1 -0
  54. package/dist/setup.js +43 -0
  55. package/dist/setup.js.map +1 -0
  56. package/flake.lock +61 -0
  57. package/package.json +1 -1
  58. package/.claude/settings.local.json +0 -36
  59. package/.codex/skills/techbook-mcp-release-prep/SKILL.md +0 -105
  60. package/.github/workflows/test.yml +0 -72
  61. package/.oxlintrc.json +0 -12
  62. package/AGENTS.md +0 -100
  63. package/deno.json +0 -3
  64. package/src/adapters/cache/memory-cache.ts +0 -31
  65. package/src/adapters/cache/null-cache.ts +0 -8
  66. package/src/adapters/html/cheerio-parser.ts +0 -50
  67. package/src/adapters/http/fetch-client.ts +0 -47
  68. package/src/adapters/http/mock-client.ts +0 -77
  69. package/src/adapters/publishers/base.ts +0 -279
  70. package/src/adapters/publishers/book-tech.ts +0 -117
  71. package/src/adapters/publishers/born-digital.ts +0 -143
  72. package/src/adapters/publishers/coronasha.ts +0 -139
  73. package/src/adapters/publishers/gihyo.ts +0 -120
  74. package/src/adapters/publishers/impress.ts +0 -103
  75. package/src/adapters/publishers/lambdanote.ts +0 -146
  76. package/src/adapters/publishers/manatee.ts +0 -113
  77. package/src/adapters/publishers/maruzen-publishing.ts +0 -129
  78. package/src/adapters/publishers/optronics.ts +0 -113
  79. package/src/adapters/publishers/oreilly-japan.ts +0 -133
  80. package/src/adapters/publishers/peaks.ts +0 -98
  81. package/src/adapters/publishers/personal-media.ts +0 -168
  82. package/src/adapters/publishers/registry.ts +0 -38
  83. package/src/adapters/publishers/rutles.ts +0 -149
  84. package/src/adapters/publishers/saiensu.ts +0 -136
  85. package/src/adapters/publishers/seshop.ts +0 -121
  86. package/src/adapters/publishers/tatsu-zine.ts +0 -154
  87. package/src/adapters/publishers/techbookfest.ts +0 -179
  88. package/src/application/get-book-detail.ts +0 -24
  89. package/src/application/search-books.ts +0 -44
  90. package/src/domain/book.ts +0 -35
  91. package/src/domain/publisher.ts +0 -18
  92. package/src/main.ts +0 -14
  93. package/src/mcp/server.ts +0 -103
  94. package/src/mcp/tools.ts +0 -54
  95. package/src/ports/cache.ts +0 -5
  96. package/src/ports/html-parser.ts +0 -15
  97. package/src/ports/http.ts +0 -17
  98. package/tests/fixtures/book-tech-detail.html +0 -51
  99. package/tests/fixtures/book-tech-search.html +0 -91
  100. package/tests/fixtures/born-digital-detail.html +0 -62
  101. package/tests/fixtures/born-digital-search.html +0 -51
  102. package/tests/fixtures/coronasha-detail.html +0 -41
  103. package/tests/fixtures/coronasha-search.html +0 -61
  104. package/tests/fixtures/gihyo-detail.html +0 -42
  105. package/tests/fixtures/gihyo-search.json +0 -54
  106. package/tests/fixtures/impress-detail-epub.html +0 -746
  107. package/tests/fixtures/impress-detail-social.html +0 -689
  108. package/tests/fixtures/lambdanote-search.html +0 -66
  109. package/tests/fixtures/manatee-detail.html +0 -53
  110. package/tests/fixtures/manatee-search.html +0 -59
  111. package/tests/fixtures/maruzen-detail.html +0 -51
  112. package/tests/fixtures/maruzen-search.html +0 -60
  113. package/tests/fixtures/optronics-detail.html +0 -30
  114. package/tests/fixtures/optronics-search.html +0 -75
  115. package/tests/fixtures/oreilly-detail.html +0 -52
  116. package/tests/fixtures/oreilly-ebook-list.html +0 -53
  117. package/tests/fixtures/peaks-detail.html +0 -39
  118. package/tests/fixtures/peaks-top.html +0 -50
  119. package/tests/fixtures/personal-media-detail.html +0 -32
  120. package/tests/fixtures/personal-media-search.html +0 -39
  121. package/tests/fixtures/rutles-detail.html +0 -32
  122. package/tests/fixtures/rutles-search.html +0 -62
  123. package/tests/fixtures/saiensu-detail.html +0 -41
  124. package/tests/fixtures/saiensu-search.html +0 -65
  125. package/tests/fixtures/seshop-detail.html +0 -45
  126. package/tests/fixtures/seshop-search.html +0 -58
  127. package/tests/fixtures/tatsu-zine-detail-free.html +0 -22
  128. package/tests/fixtures/tatsu-zine-search.html +0 -40
  129. package/tests/fixtures/techbookfest-search.json +0 -73
  130. package/tests/unit/adapters/base.test.ts +0 -441
  131. package/tests/unit/adapters/publishers/book-tech.test.ts +0 -186
  132. package/tests/unit/adapters/publishers/born-digital.test.ts +0 -194
  133. package/tests/unit/adapters/publishers/coronasha.test.ts +0 -207
  134. package/tests/unit/adapters/publishers/gihyo.test.ts +0 -137
  135. package/tests/unit/adapters/publishers/impress.test.ts +0 -129
  136. package/tests/unit/adapters/publishers/lambdanote.test.ts +0 -85
  137. package/tests/unit/adapters/publishers/manatee.test.ts +0 -165
  138. package/tests/unit/adapters/publishers/maruzen-publishing.test.ts +0 -179
  139. package/tests/unit/adapters/publishers/optronics.test.ts +0 -208
  140. package/tests/unit/adapters/publishers/oreilly-japan.test.ts +0 -194
  141. package/tests/unit/adapters/publishers/peaks.test.ts +0 -177
  142. package/tests/unit/adapters/publishers/personal-media.test.ts +0 -199
  143. package/tests/unit/adapters/publishers/rutles.test.ts +0 -173
  144. package/tests/unit/adapters/publishers/saiensu.test.ts +0 -169
  145. package/tests/unit/adapters/publishers/seshop.test.ts +0 -174
  146. package/tests/unit/adapters/publishers/tatsu-zine.test.ts +0 -172
  147. package/tests/unit/adapters/publishers/techbookfest.test.ts +0 -94
  148. package/tests/unit/adapters/registry.test.ts +0 -37
  149. package/tests/unit/application/get-book-detail.test.ts +0 -102
  150. package/tests/unit/application/search-books.test.ts +0 -137
  151. package/tsconfig.json +0 -17
@@ -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
- });
@@ -1,177 +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 { peaksAdapter } from "../../../../src/adapters/publishers/peaks.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("peaksAdapter", () => {
21
- describe("search()", () => {
22
- it("タイトルキーワードにマッチする BookRecord[] を返す", async () => {
23
- const body = await loadFixture("peaks-top.html");
24
- const http = new MockHttpClient().addResponse(
25
- "https://peaks.cc",
26
- { status: 200, body },
27
- );
28
-
29
- const results = await peaksAdapter.search({ title: "Android" }, makeDeps(http));
30
-
31
- assert.strictEqual(results.length, 1);
32
- assert.partialDeepStrictEqual(results[0], {
33
- title: "チームで育てるAndroidアプリ設計",
34
- publisher: "PEAKS",
35
- url: "https://peaks.cc/books/architecture_with_team",
36
- });
37
- });
38
-
39
- it("coverImageUrl が設定される", async () => {
40
- const body = await loadFixture("peaks-top.html");
41
- const http = new MockHttpClient().addResponse(
42
- "https://peaks.cc",
43
- { status: 200, body },
44
- );
45
-
46
- const results = await peaksAdapter.search({ title: "Android" }, makeDeps(http));
47
-
48
- assert.strictEqual(
49
- results[0].coverImageUrl,
50
- "https://peaks-img.s3-ap-northeast-1.amazonaws.com/architecture_with_team_book_cover_alpha.png",
51
- );
52
- });
53
-
54
- it("ebookStores に PEAKS (DRMフリー) が含まれる", async () => {
55
- const body = await loadFixture("peaks-top.html");
56
- const http = new MockHttpClient().addResponse(
57
- "https://peaks.cc",
58
- { status: 200, body },
59
- );
60
-
61
- const results = await peaksAdapter.search({ title: "Android" }, makeDeps(http));
62
-
63
- assert.deepStrictEqual(results[0].ebookStores, [
64
- {
65
- name: "PEAKS",
66
- url: "https://peaks.cc/books/architecture_with_team",
67
- drm: "free",
68
- },
69
- ]);
70
- });
71
-
72
- it("title が空の場合は [] を返しHTTPを呼ばない", async () => {
73
- const http = new MockHttpClient();
74
-
75
- const results = await peaksAdapter.search({}, makeDeps(http));
76
-
77
- assert.deepStrictEqual(results, []);
78
- assert.strictEqual(http.calls.length, 0);
79
- });
80
-
81
- it("author のみの検索は [] を返しHTTPを呼ばない", async () => {
82
- const http = new MockHttpClient();
83
-
84
- const results = await peaksAdapter.search({ author: "伊藤" }, makeDeps(http));
85
-
86
- assert.deepStrictEqual(results, []);
87
- assert.strictEqual(http.calls.length, 0);
88
- });
89
-
90
- it("マッチしないキーワードは空配列を返す", async () => {
91
- const body = await loadFixture("peaks-top.html");
92
- const http = new MockHttpClient().addResponse(
93
- "https://peaks.cc",
94
- { status: 200, body },
95
- );
96
-
97
- const results = await peaksAdapter.search({ title: "Python" }, makeDeps(http));
98
-
99
- assert.deepStrictEqual(results, []);
100
- });
101
- });
102
-
103
- describe("getDetail()", () => {
104
- it("詳細情報を返す", async () => {
105
- const body = await loadFixture("peaks-detail.html");
106
- const http = new MockHttpClient().addResponse(
107
- "https://peaks.cc/books/testing_with_jest",
108
- { status: 200, body },
109
- );
110
-
111
- const book = await peaksAdapter.getDetail(
112
- "https://peaks.cc/books/testing_with_jest",
113
- makeDeps(http),
114
- );
115
-
116
- assert.partialDeepStrictEqual(book, {
117
- title: "Jestではじめるテスト入門",
118
- publisher: "PEAKS",
119
- price: 2900,
120
- });
121
- });
122
-
123
- it("複数著者を取得し末尾カンマを除去する", async () => {
124
- const body = await loadFixture("peaks-detail.html");
125
- const http = new MockHttpClient().addResponse(
126
- "https://peaks.cc/books/testing_with_jest",
127
- { status: 200, body },
128
- );
129
-
130
- const book = await peaksAdapter.getDetail(
131
- "https://peaks.cc/books/testing_with_jest",
132
- makeDeps(http),
133
- );
134
-
135
- assert.deepStrictEqual(book.authors, ["伊藤 貴之", "椎葉 光行"]);
136
- });
137
-
138
- it("coverImageUrl が設定される", async () => {
139
- const body = await loadFixture("peaks-detail.html");
140
- const http = new MockHttpClient().addResponse(
141
- "https://peaks.cc/books/testing_with_jest",
142
- { status: 200, body },
143
- );
144
-
145
- const book = await peaksAdapter.getDetail(
146
- "https://peaks.cc/books/testing_with_jest",
147
- makeDeps(http),
148
- );
149
-
150
- assert.strictEqual(
151
- book.coverImageUrl,
152
- "https://peaks-img.s3-ap-northeast-1.amazonaws.com/testing_with_jest_twittercard.png",
153
- );
154
- });
155
-
156
- it("ebookStores に PEAKS (DRMフリー) が含まれる", async () => {
157
- const body = await loadFixture("peaks-detail.html");
158
- const http = new MockHttpClient().addResponse(
159
- "https://peaks.cc/books/testing_with_jest",
160
- { status: 200, body },
161
- );
162
-
163
- const book = await peaksAdapter.getDetail(
164
- "https://peaks.cc/books/testing_with_jest",
165
- makeDeps(http),
166
- );
167
-
168
- assert.deepStrictEqual(book.ebookStores, [
169
- {
170
- name: "PEAKS",
171
- url: "https://peaks.cc/books/testing_with_jest",
172
- drm: "free",
173
- },
174
- ]);
175
- });
176
- });
177
- });
@@ -1,199 +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 { personalMediaAdapter } from "../../../../src/adapters/publishers/personal-media.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("personalMediaAdapter", () => {
21
- describe("search()", () => {
22
- it("タイトルキーワードに一致する書籍のみ返す", async () => {
23
- const body = await loadFixture("personal-media-search.html");
24
- const http = new MockHttpClient().addResponse(
25
- "https://www.personal-media.co.jp/webshop/book/",
26
- { status: 200, body },
27
- );
28
-
29
- const results = await personalMediaAdapter.search({ title: "TRON" }, makeDeps(http));
30
-
31
- // フィクスチャには TRON 含む2件・含まない1件あり
32
- assert.strictEqual(results.length, 2);
33
- });
34
-
35
- it("書籍タイトルが正しく取得される", async () => {
36
- const body = await loadFixture("personal-media-search.html");
37
- const http = new MockHttpClient().addResponse(
38
- "https://www.personal-media.co.jp/webshop/book/",
39
- { status: 200, body },
40
- );
41
-
42
- const results = await personalMediaAdapter.search({ title: "TRON" }, makeDeps(http));
43
-
44
- assert.strictEqual(results[0].title, "μITRON4.0標準ガイドブック(PDF版)");
45
- });
46
-
47
- it("税込価格が取得される", async () => {
48
- const body = await loadFixture("personal-media-search.html");
49
- const http = new MockHttpClient().addResponse(
50
- "https://www.personal-media.co.jp/webshop/book/",
51
- { status: 200, body },
52
- );
53
-
54
- const results = await personalMediaAdapter.search({ title: "TRON" }, makeDeps(http));
55
-
56
- assert.strictEqual(results[0].price, 2585);
57
- });
58
-
59
- it("url が書籍詳細ページの絶対URLになる", async () => {
60
- const body = await loadFixture("personal-media-search.html");
61
- const http = new MockHttpClient().addResponse(
62
- "https://www.personal-media.co.jp/webshop/book/",
63
- { status: 200, body },
64
- );
65
-
66
- const results = await personalMediaAdapter.search({ title: "TRON" }, makeDeps(http));
67
-
68
- assert.strictEqual(
69
- results[0].url,
70
- "https://www.personal-media.co.jp/book/tron/191.html",
71
- );
72
- });
73
-
74
- it("publisher が パーソナルメディア になる", async () => {
75
- const body = await loadFixture("personal-media-search.html");
76
- const http = new MockHttpClient().addResponse(
77
- "https://www.personal-media.co.jp/webshop/book/",
78
- { status: 200, body },
79
- );
80
-
81
- const results = await personalMediaAdapter.search({ title: "TRON" }, makeDeps(http));
82
-
83
- assert.strictEqual(results[0].publisher, "パーソナルメディア");
84
- });
85
-
86
- it("title が空の場合は [] を返しHTTPを呼ばない", async () => {
87
- const http = new MockHttpClient();
88
-
89
- const results = await personalMediaAdapter.search({}, makeDeps(http));
90
-
91
- assert.deepStrictEqual(results, []);
92
- assert.strictEqual(http.calls.length, 0);
93
- });
94
-
95
- it("author のみ指定の場合も [] を返しHTTPを呼ばない", async () => {
96
- const http = new MockHttpClient();
97
-
98
- const results = await personalMediaAdapter.search({ author: "坂村健" }, makeDeps(http));
99
-
100
- assert.deepStrictEqual(results, []);
101
- assert.strictEqual(http.calls.length, 0);
102
- });
103
- });
104
-
105
- describe("getDetail()", () => {
106
- it("タイトルを返す", async () => {
107
- const body = await loadFixture("personal-media-detail.html");
108
- const http = new MockHttpClient().addResponse(
109
- "https://www.personal-media.co.jp/book/tron/191.html",
110
- { status: 200, body },
111
- );
112
-
113
- const book = await personalMediaAdapter.getDetail(
114
- "https://www.personal-media.co.jp/book/tron/191.html",
115
- makeDeps(http),
116
- );
117
-
118
- assert.strictEqual(book.title, "μITRON4.0標準ガイドブック");
119
- });
120
-
121
- it("著者名から役割語を除去して返す", async () => {
122
- const body = await loadFixture("personal-media-detail.html");
123
- const http = new MockHttpClient().addResponse(
124
- "https://www.personal-media.co.jp/book/tron/191.html",
125
- { status: 200, body },
126
- );
127
-
128
- const book = await personalMediaAdapter.getDetail(
129
- "https://www.personal-media.co.jp/book/tron/191.html",
130
- makeDeps(http),
131
- );
132
-
133
- assert.deepStrictEqual(book.authors, ["坂村 健", "社団法人トロン協会"]);
134
- });
135
-
136
- it("価格・ISBN・発行日を返す", async () => {
137
- const body = await loadFixture("personal-media-detail.html");
138
- const http = new MockHttpClient().addResponse(
139
- "https://www.personal-media.co.jp/book/tron/191.html",
140
- { status: 200, body },
141
- );
142
-
143
- const book = await personalMediaAdapter.getDetail(
144
- "https://www.personal-media.co.jp/book/tron/191.html",
145
- makeDeps(http),
146
- );
147
-
148
- assert.partialDeepStrictEqual(book, {
149
- price: 3520,
150
- isbn: "9784893621917",
151
- publishedAt: "2001-11-01",
152
- });
153
- });
154
-
155
- it("coverImageUrl が絶対URLになる", async () => {
156
- const body = await loadFixture("personal-media-detail.html");
157
- const http = new MockHttpClient().addResponse(
158
- "https://www.personal-media.co.jp/book/tron/191.html",
159
- { status: 200, body },
160
- );
161
-
162
- const book = await personalMediaAdapter.getDetail(
163
- "https://www.personal-media.co.jp/book/tron/191.html",
164
- makeDeps(http),
165
- );
166
-
167
- assert.strictEqual(
168
- book.coverImageUrl,
169
- "https://www.personal-media.co.jp/book/tron/images/191_l.jpg",
170
- );
171
- });
172
-
173
- it("ebookStores に PDF版(social)と Smooth Reader版(drm)が含まれる", async () => {
174
- const body = await loadFixture("personal-media-detail.html");
175
- const http = new MockHttpClient().addResponse(
176
- "https://www.personal-media.co.jp/book/tron/191.html",
177
- { status: 200, body },
178
- );
179
-
180
- const book = await personalMediaAdapter.getDetail(
181
- "https://www.personal-media.co.jp/book/tron/191.html",
182
- makeDeps(http),
183
- );
184
-
185
- assert.deepStrictEqual(book.ebookStores, [
186
- {
187
- name: "パーソナルメディア",
188
- url: "https://www.personal-media.co.jp/webshop/book/20191.html",
189
- drm: "social",
190
- },
191
- {
192
- name: "Smooth Reader",
193
- url: "https://www.personal-media.co.jp/smoothreader/store/tron/191.html",
194
- drm: "drm",
195
- },
196
- ]);
197
- });
198
- });
199
- });