@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,85 +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 { lambdanoteAdapter } from "../../../../src/adapters/publishers/lambdanote.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
- describe("lambdanoteAdapter", () => {
17
- it("search() が HTML 検索結果から BookRecord[] を返す", async () => {
18
- const body = await readFile(join(FIXTURES_DIR, "lambdanote-search.html"), "utf-8");
19
- const http = new MockHttpClient().addResponse(
20
- "https://www.lambdanote.com/search",
21
- { status: 200, body },
22
- );
23
-
24
- const results = await lambdanoteAdapter.search({ title: "Go" }, makeDeps(http));
25
-
26
- assert.strictEqual(results.length, 2);
27
- assert.partialDeepStrictEqual(results[0], {
28
- title: "Goならわかるシステムプログラミング 第2版",
29
- publisher: "ラムダノート",
30
- price: 3960,
31
- });
32
- assert.strictEqual(results[0].url, "https://www.lambdanote.com/products/go-2");
33
- assert.ok(results[0].coverImageUrl?.includes("go2_small.jpg"));
34
- });
35
-
36
- it("2件目の書籍も正しく取得できる", async () => {
37
- const body = await readFile(join(FIXTURES_DIR, "lambdanote-search.html"), "utf-8");
38
- const http = new MockHttpClient().addResponse(
39
- "https://www.lambdanote.com/search",
40
- { status: 200, body },
41
- );
42
-
43
- const results = await lambdanoteAdapter.search({ title: "Go" }, makeDeps(http));
44
-
45
- assert.partialDeepStrictEqual(results[1], {
46
- title: "プログラミング言語Go",
47
- price: 4400,
48
- });
49
- assert.strictEqual(results[1].url, "https://www.lambdanote.com/products/gopl");
50
- });
51
-
52
- it("limit を適用する", async () => {
53
- const body = await readFile(join(FIXTURES_DIR, "lambdanote-search.html"), "utf-8");
54
- const http = new MockHttpClient().addResponse(
55
- "https://www.lambdanote.com/search",
56
- { status: 200, body },
57
- );
58
-
59
- const results = await lambdanoteAdapter.search({ title: "Go", limit: 1 }, makeDeps(http));
60
-
61
- assert.strictEqual(results.length, 1);
62
- });
63
-
64
- it("結果ゼロの場合は [] を返す", async () => {
65
- const body = `<!DOCTYPE html><html><body>
66
- <ul class="search-results__list"></ul>
67
- </body></html>`;
68
- const http = new MockHttpClient().addResponse(
69
- "https://www.lambdanote.com/search",
70
- { status: 200, body },
71
- );
72
-
73
- const results = await lambdanoteAdapter.search({ title: "存在しない本" }, makeDeps(http));
74
-
75
- assert.deepStrictEqual(results, []);
76
- });
77
-
78
- it("title も author も空の場合は [] を返しHTTPを呼ばない", async () => {
79
- const http = new MockHttpClient();
80
- const results = await lambdanoteAdapter.search({}, makeDeps(http));
81
-
82
- assert.deepStrictEqual(results, []);
83
- assert.strictEqual(http.calls.length, 0);
84
- });
85
- });
@@ -1,165 +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 { manateeAdapter } from "../../../../src/adapters/publishers/manatee.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("manateeAdapter", () => {
21
- describe("search()", () => {
22
- it("検索結果から BookRecord[] を返す", async () => {
23
- const body = await loadFixture("manatee-search.html");
24
- const http = new MockHttpClient().addResponse(
25
- "https://book.mynavi.jp/manatee/books/",
26
- { status: 200, body },
27
- );
28
-
29
- const results = await manateeAdapter.search({ title: "TypeScript" }, makeDeps(http));
30
-
31
- assert.strictEqual(results.length, 3);
32
- assert.partialDeepStrictEqual(results[0], {
33
- title: "現場で使えるTypeScript 詳解実践ガイド",
34
- publisher: "マナティ",
35
- url: "https://book.mynavi.jp/manatee/books/detail/id=142711",
36
- price: 2948,
37
- });
38
- });
39
-
40
- it("ebookStores にマナティ(ソーシャルDRM)が含まれる", async () => {
41
- const body = await loadFixture("manatee-search.html");
42
- const http = new MockHttpClient().addResponse(
43
- "https://book.mynavi.jp/manatee/books/",
44
- { status: 200, body },
45
- );
46
-
47
- const results = await manateeAdapter.search({ title: "TypeScript" }, makeDeps(http));
48
-
49
- assert.deepStrictEqual(results[0].ebookStores, [
50
- {
51
- name: "マナティ",
52
- url: "https://book.mynavi.jp/manatee/books/detail/id=142711",
53
- drm: "social",
54
- },
55
- ]);
56
- });
57
-
58
- it("coverImageUrl が設定される", async () => {
59
- const body = await loadFixture("manatee-search.html");
60
- const http = new MockHttpClient().addResponse(
61
- "https://book.mynavi.jp/manatee/books/",
62
- { status: 200, body },
63
- );
64
-
65
- const results = await manateeAdapter.search({ title: "TypeScript" }, makeDeps(http));
66
-
67
- assert.strictEqual(
68
- results[0].coverImageUrl,
69
- "https://book.mynavi.jp/files/topics/142711_ext_06_0.jpg",
70
- );
71
- });
72
-
73
- it("limit を適用する", async () => {
74
- const body = await loadFixture("manatee-search.html");
75
- const http = new MockHttpClient().addResponse(
76
- "https://book.mynavi.jp/manatee/books/",
77
- { status: 200, body },
78
- );
79
-
80
- const results = await manateeAdapter.search({ title: "TypeScript", limit: 2 }, makeDeps(http));
81
-
82
- assert.strictEqual(results.length, 2);
83
- });
84
-
85
- it("title も author も空の場合は [] を返しHTTPを呼ばない", async () => {
86
- const http = new MockHttpClient();
87
-
88
- const results = await manateeAdapter.search({}, makeDeps(http));
89
-
90
- assert.deepStrictEqual(results, []);
91
- assert.strictEqual(http.calls.length, 0);
92
- });
93
-
94
- it("検索リクエストに topics_keyword が含まれる", async () => {
95
- const body = await loadFixture("manatee-search.html");
96
- const http = new MockHttpClient().addResponse(
97
- "https://book.mynavi.jp/manatee/books/",
98
- { status: 200, body },
99
- );
100
-
101
- await manateeAdapter.search({ title: "TypeScript" }, makeDeps(http));
102
-
103
- assert.ok(http.calls[0].includes("topics_keyword=TypeScript"));
104
- });
105
- });
106
-
107
- describe("getDetail()", () => {
108
- it("詳細情報を返す", async () => {
109
- const body = await loadFixture("manatee-detail.html");
110
- const http = new MockHttpClient().addResponse(
111
- "https://book.mynavi.jp/manatee/books/detail/id=142711",
112
- { status: 200, body },
113
- );
114
-
115
- const book = await manateeAdapter.getDetail(
116
- "https://book.mynavi.jp/manatee/books/detail/id=142711",
117
- makeDeps(http),
118
- );
119
-
120
- assert.partialDeepStrictEqual(book, {
121
- title: "現場で使えるTypeScript 詳解実践ガイド",
122
- publisher: "マイナビ出版",
123
- isbn: "9784839984274",
124
- price: 2948,
125
- publishedAt: "2024-03-22",
126
- });
127
- });
128
-
129
- it("著者が配列で返される", async () => {
130
- const body = await loadFixture("manatee-detail.html");
131
- const http = new MockHttpClient().addResponse(
132
- "https://book.mynavi.jp/manatee/books/detail/id=142711",
133
- { status: 200, body },
134
- );
135
-
136
- const book = await manateeAdapter.getDetail(
137
- "https://book.mynavi.jp/manatee/books/detail/id=142711",
138
- makeDeps(http),
139
- );
140
-
141
- assert.deepStrictEqual(book.authors, ["菅原浩之", "CodeMafia", "外村将大"]);
142
- });
143
-
144
- it("ebookStores にマナティ(ソーシャルDRM)が含まれる", async () => {
145
- const body = await loadFixture("manatee-detail.html");
146
- const http = new MockHttpClient().addResponse(
147
- "https://book.mynavi.jp/manatee/books/detail/id=142711",
148
- { status: 200, body },
149
- );
150
-
151
- const book = await manateeAdapter.getDetail(
152
- "https://book.mynavi.jp/manatee/books/detail/id=142711",
153
- makeDeps(http),
154
- );
155
-
156
- assert.deepStrictEqual(book.ebookStores, [
157
- {
158
- name: "マナティ",
159
- url: "https://book.mynavi.jp/manatee/books/detail/id=142711",
160
- drm: "social",
161
- },
162
- ]);
163
- });
164
- });
165
- });
@@ -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
- });