@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,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
- });
@@ -1,173 +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 { rutlesAdapter } from "../../../../src/adapters/publishers/rutles.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("rutlesAdapter", () => {
21
- describe("search()", () => {
22
- it("【電子版】のみ BookRecord[] を返す(紙書籍は除外)", async () => {
23
- const body = await loadFixture("rutles-search.html");
24
- const http = new MockHttpClient().addResponse(
25
- "https://shop.rutles.net/",
26
- { status: 200, body },
27
- );
28
-
29
- const results = await rutlesAdapter.search({ title: "C言語" }, makeDeps(http));
30
-
31
- // フィクスチャには電子2件・紙1件、電子のみ返す
32
- assert.strictEqual(results.length, 2);
33
- assert.partialDeepStrictEqual(results[0], {
34
- title: "【電子版】C言語3Dゲームプログラミング教室",
35
- publisher: "ラトルズ",
36
- url: "https://shop.rutles.net/?pid=173173393",
37
- price: 3168,
38
- });
39
- });
40
-
41
- it("coverImageUrl が設定される", async () => {
42
- const body = await loadFixture("rutles-search.html");
43
- const http = new MockHttpClient().addResponse(
44
- "https://shop.rutles.net/",
45
- { status: 200, body },
46
- );
47
-
48
- const results = await rutlesAdapter.search({ title: "C言語" }, makeDeps(http));
49
-
50
- assert.strictEqual(
51
- results[0].coverImageUrl,
52
- "https://img21.shop-pro.jp/PA01496/800/product/173173393_th.jpg",
53
- );
54
- });
55
-
56
- it("ebookStores にラトルズ(DRMフリー)が含まれる", async () => {
57
- const body = await loadFixture("rutles-search.html");
58
- const http = new MockHttpClient().addResponse(
59
- "https://shop.rutles.net/",
60
- { status: 200, body },
61
- );
62
-
63
- const results = await rutlesAdapter.search({ title: "C言語" }, makeDeps(http));
64
-
65
- assert.deepStrictEqual(results[0].ebookStores, [
66
- {
67
- name: "ラトルズ",
68
- url: "https://shop.rutles.net/?pid=173173393",
69
- drm: "free",
70
- },
71
- ]);
72
- });
73
-
74
- it("title も author も空の場合は [] を返しHTTPを呼ばない", async () => {
75
- const http = new MockHttpClient();
76
-
77
- const results = await rutlesAdapter.search({}, makeDeps(http));
78
-
79
- assert.deepStrictEqual(results, []);
80
- assert.strictEqual(http.calls.length, 0);
81
- });
82
-
83
- it("検索URLに EUC-JP エンコードされたキーワードが含まれる", async () => {
84
- const body = await loadFixture("rutles-search.html");
85
- const http = new MockHttpClient().addResponse(
86
- "https://shop.rutles.net/",
87
- { status: 200, body },
88
- );
89
-
90
- await rutlesAdapter.search({ title: "言語" }, makeDeps(http));
91
-
92
- // "言語" の EUC-JP エンコード = %B8%C0%B8%EC
93
- assert.ok(http.calls[0].includes("%B8%C0%B8%EC"));
94
- });
95
- });
96
-
97
- describe("getDetail()", () => {
98
- it("詳細情報を返す", async () => {
99
- const body = await loadFixture("rutles-detail.html");
100
- const http = new MockHttpClient().addResponse(
101
- "https://shop.rutles.net/?pid=173173393",
102
- { status: 200, body },
103
- );
104
-
105
- const book = await rutlesAdapter.getDetail(
106
- "https://shop.rutles.net/?pid=173173393",
107
- makeDeps(http),
108
- );
109
-
110
- assert.partialDeepStrictEqual(book, {
111
- title: "【電子版】C言語3Dゲームプログラミング教室",
112
- publisher: "ラトルズ",
113
- isbn: "9784899774211",
114
- price: 3168,
115
- publishedAt: "2014-10-25",
116
- });
117
- });
118
-
119
- it("著者の役割語・所属を除去する", async () => {
120
- const body = await loadFixture("rutles-detail.html");
121
- const http = new MockHttpClient().addResponse(
122
- "https://shop.rutles.net/?pid=173173393",
123
- { status: 200, body },
124
- );
125
-
126
- const book = await rutlesAdapter.getDetail(
127
- "https://shop.rutles.net/?pid=173173393",
128
- makeDeps(http),
129
- );
130
-
131
- assert.deepStrictEqual(book.authors, ["大槻有一郎", "山田巧"]);
132
- });
133
-
134
- it("coverImageUrl が設定される", async () => {
135
- const body = await loadFixture("rutles-detail.html");
136
- const http = new MockHttpClient().addResponse(
137
- "https://shop.rutles.net/?pid=173173393",
138
- { status: 200, body },
139
- );
140
-
141
- const book = await rutlesAdapter.getDetail(
142
- "https://shop.rutles.net/?pid=173173393",
143
- makeDeps(http),
144
- );
145
-
146
- assert.strictEqual(
147
- book.coverImageUrl,
148
- "https://img21.shop-pro.jp/PA01496/800/product/173173393.jpg",
149
- );
150
- });
151
-
152
- it("ebookStores にラトルズ(DRMフリー)が含まれる", async () => {
153
- const body = await loadFixture("rutles-detail.html");
154
- const http = new MockHttpClient().addResponse(
155
- "https://shop.rutles.net/?pid=173173393",
156
- { status: 200, body },
157
- );
158
-
159
- const book = await rutlesAdapter.getDetail(
160
- "https://shop.rutles.net/?pid=173173393",
161
- makeDeps(http),
162
- );
163
-
164
- assert.deepStrictEqual(book.ebookStores, [
165
- {
166
- name: "ラトルズ",
167
- url: "https://shop.rutles.net/?pid=173173393",
168
- drm: "free",
169
- },
170
- ]);
171
- });
172
- });
173
- });