@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,176 +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 { getBookByIsbn } from "../../../src/application/get-book-by-isbn.js";
6
- import type { PublisherAdapter, PublisherDeps } from "../../../src/domain/publisher.js";
7
- import type { BookRecord } from "../../../src/domain/book.js";
8
- import { MockHttpClient } from "../../../src/adapters/http/mock-client.js";
9
- import { CheerioHtmlParser } from "../../../src/adapters/html/cheerio-parser.js";
10
- import { NullCacheStore } from "../../../src/adapters/cache/null-cache.js";
11
-
12
- const FIXTURES_DIR = join(import.meta.dirname, "../../fixtures");
13
-
14
- /** ランタイム非依存の最小モック関数 */
15
- function mockFn<T>(impl: (...args: unknown[]) => T = () => undefined as T) {
16
- const _calls: { arguments: unknown[] }[] = [];
17
- const fn = Object.assign(
18
- (...args: unknown[]) => {
19
- _calls.push({ arguments: args });
20
- return impl(...args);
21
- },
22
- { mock: { calls: _calls, callCount: () => _calls.length } },
23
- );
24
- return fn;
25
- }
26
-
27
- // openBD が返す storelink と一致する baseUrl を持つアダプター
28
- const LAMBDANOTE_BASE_URL = "https://www.lambdanote.com";
29
- const LAMBDANOTE_STORELINK = "https://www.lambdanote.com/collections/type-systems";
30
-
31
- function makeDetailBook(): BookRecord {
32
- return {
33
- title: "型システムのしくみ(出版社サイト取得)",
34
- authors: ["遠藤侑介"],
35
- publisher: "ラムダノート",
36
- url: LAMBDANOTE_STORELINK,
37
- isbn: "9784908686207",
38
- price: 3300,
39
- };
40
- }
41
-
42
- function makeAdapter(baseUrl: string, book: BookRecord): PublisherAdapter {
43
- return {
44
- id: "lambdanote",
45
- name: "ラムダノート",
46
- baseUrl,
47
- search: mockFn(),
48
- getDetail: mockFn(async () => book),
49
- };
50
- }
51
-
52
- async function makeOpenBDDeps(http: MockHttpClient): Promise<PublisherDeps> {
53
- return { http, parser: new CheerioHtmlParser(), cache: new NullCacheStore() };
54
- }
55
-
56
- async function makeHttpWithOpenBD(): Promise<MockHttpClient> {
57
- const openBDBody = await readFile(join(FIXTURES_DIR, "openbd-response.json"), "utf-8");
58
- // hanmoto.storelink を追加してフィクスチャを加工
59
- const data = JSON.parse(openBDBody);
60
- data[0].hanmoto = { isbn: "9784908686207", storelink: LAMBDANOTE_STORELINK };
61
- return new MockHttpClient().addResponse(
62
- "https://api.openbd.jp/v1/get",
63
- { status: 200, body: JSON.stringify(data) },
64
- );
65
- }
66
-
67
- describe("getBookByIsbn()", () => {
68
- it("storelink が既知アダプターと一致する場合は getDetail() を呼ぶ", async () => {
69
- const http = await makeHttpWithOpenBD();
70
- const deps = await makeOpenBDDeps(http);
71
- const detailBook = makeDetailBook();
72
- const adapter = makeAdapter(LAMBDANOTE_BASE_URL, detailBook);
73
-
74
- const result = await getBookByIsbn("9784908686207", [adapter], deps);
75
-
76
- assert.strictEqual(result.title, "型システムのしくみ(出版社サイト取得)");
77
- assert.strictEqual(
78
- (adapter.getDetail as ReturnType<typeof mockFn>).mock.calls[0].arguments[0],
79
- LAMBDANOTE_STORELINK,
80
- );
81
- });
82
-
83
- it("getDetail() が失敗した場合は openBD データで返す", async () => {
84
- const http = await makeHttpWithOpenBD();
85
- const deps = await makeOpenBDDeps(http);
86
- const adapter: PublisherAdapter = {
87
- id: "lambdanote",
88
- name: "ラムダノート",
89
- baseUrl: LAMBDANOTE_BASE_URL,
90
- search: mockFn(),
91
- getDetail: mockFn(async () => { throw new Error("詳細取得失敗"); }),
92
- };
93
-
94
- const result = await getBookByIsbn("9784908686207", [adapter], deps);
95
-
96
- // フォールバックとして openBD のデータが返る
97
- assert.strictEqual(result.isbn, "9784908686207");
98
- assert.strictEqual(result.publisher, "ラムダノート");
99
- assert.strictEqual(result.url, LAMBDANOTE_STORELINK);
100
- });
101
-
102
- it("storelink が既知アダプターと一致しない場合は openBD データで返す", async () => {
103
- const openBDBody = await readFile(join(FIXTURES_DIR, "openbd-response.json"), "utf-8");
104
- const data = JSON.parse(openBDBody);
105
- data[0].hanmoto = { isbn: "9784908686207", storelink: "https://unknown-store.example.com/books/1" };
106
- const http = new MockHttpClient().addResponse(
107
- "https://api.openbd.jp/v1/get",
108
- { status: 200, body: JSON.stringify(data) },
109
- );
110
- const deps = await makeOpenBDDeps(http);
111
- const adapter = makeAdapter(LAMBDANOTE_BASE_URL, makeDetailBook());
112
-
113
- const result = await getBookByIsbn("9784908686207", [adapter], deps);
114
-
115
- assert.strictEqual(result.isbn, "9784908686207");
116
- assert.strictEqual((adapter.getDetail as ReturnType<typeof mockFn>).mock.callCount(), 0);
117
- });
118
-
119
- it("storelink がない場合は openBD データで返す", async () => {
120
- const openBDBody = await readFile(join(FIXTURES_DIR, "openbd-response.json"), "utf-8");
121
- // storelink を持たない hanmoto に差し替え
122
- const data = JSON.parse(openBDBody);
123
- data[0].hanmoto = { isbn: "9784908686207" };
124
- const http = new MockHttpClient().addResponse(
125
- "https://api.openbd.jp/v1/get",
126
- { status: 200, body: JSON.stringify(data) },
127
- );
128
- const deps = await makeOpenBDDeps(http);
129
- const adapter = makeAdapter(LAMBDANOTE_BASE_URL, makeDetailBook());
130
-
131
- const result = await getBookByIsbn("9784908686207", [adapter], deps);
132
-
133
- assert.strictEqual(result.isbn, "9784908686207");
134
- assert.strictEqual((adapter.getDetail as ReturnType<typeof mockFn>).mock.callCount(), 0);
135
- });
136
-
137
- it("openBD に存在しない ISBN はエラーをスローする", async () => {
138
- const http = new MockHttpClient().addResponse(
139
- "https://api.openbd.jp/v1/get",
140
- { status: 200, body: JSON.stringify([null]) },
141
- );
142
- const deps = await makeOpenBDDeps(http);
143
-
144
- await assert.rejects(
145
- getBookByIsbn("9780000000000", [], deps),
146
- /書誌情報が見つかりません/,
147
- );
148
- });
149
-
150
- it("ISBNのハイフンを除去して正規化する", async () => {
151
- const http = await makeHttpWithOpenBD();
152
- const deps = await makeOpenBDDeps(http);
153
-
154
- // ハイフンありで渡しても openBD は正規化されたISBNで照合される
155
- await getBookByIsbn("978-4-908686-20-7", [makeAdapter(LAMBDANOTE_BASE_URL, makeDetailBook())], deps);
156
-
157
- assert.ok(deps.http.calls[0].includes("9784908686207"));
158
- });
159
-
160
- it("openBD データから著者が分割される", async () => {
161
- const openBDBody = await readFile(join(FIXTURES_DIR, "openbd-response.json"), "utf-8");
162
- const data = JSON.parse(openBDBody);
163
- // 複数著者をスラッシュ区切りで設定
164
- data[0].summary.author = "著者A/著者B";
165
- data[0].hanmoto = undefined;
166
- const http = new MockHttpClient().addResponse(
167
- "https://api.openbd.jp/v1/get",
168
- { status: 200, body: JSON.stringify(data) },
169
- );
170
- const deps = await makeOpenBDDeps(http);
171
-
172
- const result = await getBookByIsbn("9784908686207", [], deps);
173
-
174
- assert.deepStrictEqual(result.authors, ["著者A", "著者B"]);
175
- });
176
- });
@@ -1,102 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { getBookDetail } from "../../../src/application/get-book-detail.js";
4
- import type { PublisherAdapter, PublisherDeps } from "../../../src/domain/publisher.js";
5
- import type { BookRecord } from "../../../src/domain/book.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
- /** ランタイム非依存の最小モック関数 */
11
- function mockFn<T>(impl: (...args: unknown[]) => T = () => undefined as T) {
12
- const _calls: { arguments: unknown[] }[] = [];
13
- const fn = Object.assign(
14
- (...args: unknown[]) => {
15
- _calls.push({ arguments: args });
16
- return impl(...args);
17
- },
18
- { mock: { calls: _calls, callCount: () => _calls.length } },
19
- );
20
- return fn;
21
- }
22
-
23
- function makeDeps(): PublisherDeps {
24
- return {
25
- http: new MockHttpClient(),
26
- parser: new CheerioHtmlParser(),
27
- cache: new NullCacheStore(),
28
- };
29
- }
30
-
31
- function makeBook(overrides: Partial<BookRecord> = {}): BookRecord {
32
- return {
33
- title: "テスト本",
34
- authors: ["著者名"],
35
- publisher: "テスト社",
36
- url: "https://example.com/book/1",
37
- ...overrides,
38
- };
39
- }
40
-
41
- function makeAdapter(baseUrl: string, book: BookRecord): PublisherAdapter {
42
- return {
43
- id: "test",
44
- name: "テスト社",
45
- baseUrl,
46
- search: mockFn(),
47
- getDetail: mockFn(async () => book),
48
- };
49
- }
50
-
51
- describe("getBookDetail()", () => {
52
- it("URLに対応するアダプターの getDetail() を呼んで結果を返す", async () => {
53
- const book = makeBook({ title: "詳細情報テスト" });
54
- const adapter = makeAdapter("https://example.com", book);
55
- const url = "https://example.com/book/42";
56
-
57
- const result = await getBookDetail(url, [adapter], makeDeps());
58
-
59
- assert.deepStrictEqual(result, book);
60
- assert.strictEqual(
61
- (adapter.getDetail as ReturnType<typeof mockFn>).mock.calls[0].arguments[0],
62
- url,
63
- );
64
- });
65
-
66
- it("baseUrl が前方一致するアダプターを選択する", async () => {
67
- const bookA = makeBook({ title: "A社の本" });
68
- const bookB = makeBook({ title: "B社の本" });
69
- const adapterA = makeAdapter("https://a.example.com", bookA);
70
- const adapterB = makeAdapter("https://b.example.com", bookB);
71
-
72
- const result = await getBookDetail("https://b.example.com/book/1", [adapterA, adapterB], makeDeps());
73
-
74
- assert.strictEqual(result.title, "B社の本");
75
- assert.strictEqual((adapterA.getDetail as ReturnType<typeof mockFn>).mock.callCount(), 0);
76
- });
77
-
78
- it("対応するアダプターがなければエラーをスローする", async () => {
79
- const adapter = makeAdapter("https://other.example.com", makeBook());
80
-
81
- await assert.rejects(
82
- getBookDetail("https://unknown.example.com/book/1", [adapter], makeDeps()),
83
- /このURLに対応する出版社アダプターがありません/,
84
- );
85
- });
86
-
87
- it("エラーメッセージに対応URLリストを含む", async () => {
88
- const adapter = makeAdapter("https://example.com", makeBook());
89
-
90
- await assert.rejects(
91
- getBookDetail("https://unknown.example.com/book/1", [adapter], makeDeps()),
92
- /https:\/\/example\.com/,
93
- );
94
- });
95
-
96
- it("アダプターが空のときエラーをスローする", async () => {
97
- await assert.rejects(
98
- getBookDetail("https://example.com/book/1", [], makeDeps()),
99
- /このURLに対応する出版社アダプターがありません/,
100
- );
101
- });
102
- });
@@ -1,137 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { searchBooks } from "../../../src/application/search-books.js";
4
- import type { PublisherAdapter, PublisherDeps } from "../../../src/domain/publisher.js";
5
- import type { BookRecord } from "../../../src/domain/book.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
- /** ランタイム非依存の最小モック関数 */
11
- function mockFn<T>(impl: (...args: unknown[]) => T = () => undefined as T) {
12
- const _calls: { arguments: unknown[] }[] = [];
13
- const fn = Object.assign(
14
- (...args: unknown[]) => {
15
- _calls.push({ arguments: args });
16
- return impl(...args);
17
- },
18
- { mock: { calls: _calls, callCount: () => _calls.length } },
19
- );
20
- return fn;
21
- }
22
-
23
- function makeDeps(): PublisherDeps {
24
- return {
25
- http: new MockHttpClient(),
26
- parser: new CheerioHtmlParser(),
27
- cache: new NullCacheStore(),
28
- };
29
- }
30
-
31
- function makeBook(overrides: Partial<BookRecord> = {}): BookRecord {
32
- return {
33
- title: "テスト本",
34
- authors: ["著者名"],
35
- publisher: "テスト社",
36
- url: "https://example.com/book/1",
37
- ...overrides,
38
- };
39
- }
40
-
41
- function makeAdapter(id: string, books: BookRecord[]): PublisherAdapter {
42
- return {
43
- id,
44
- name: `${id} 出版社`,
45
- baseUrl: `https://${id}.example.com`,
46
- search: mockFn(async () => books),
47
- getDetail: mockFn(),
48
- };
49
- }
50
-
51
- describe("searchBooks()", () => {
52
- it("全アダプターの結果を結合して返す", async () => {
53
- const book1 = makeBook({ title: "本A", url: "https://a.example.com/1" });
54
- const book2 = makeBook({ title: "本B", url: "https://b.example.com/1" });
55
- const publishers = [makeAdapter("a", [book1]), makeAdapter("b", [book2])];
56
-
57
- const { books, errors } = await searchBooks({ title: "テスト" }, publishers, makeDeps());
58
-
59
- assert.strictEqual(books.length, 2);
60
- assert.strictEqual(books[0].title, "本A");
61
- assert.strictEqual(books[1].title, "本B");
62
- assert.strictEqual(errors.length, 0);
63
- });
64
-
65
- it("publisherId が指定された場合は該当アダプターのみ呼ぶ", async () => {
66
- const book = makeBook({ title: "本A" });
67
- const adapterA = makeAdapter("a", [book]);
68
- const adapterB = makeAdapter("b", []);
69
- const publishers = [adapterA, adapterB];
70
-
71
- const { books } = await searchBooks({ title: "テスト", publisherId: "a" }, publishers, makeDeps());
72
-
73
- assert.strictEqual(books.length, 1);
74
- assert.strictEqual((adapterA.search as ReturnType<typeof mockFn>).mock.callCount(), 1);
75
- assert.strictEqual((adapterB.search as ReturnType<typeof mockFn>).mock.callCount(), 0);
76
- });
77
-
78
- it("1つのアダプターが失敗しても他の結果は返す", async () => {
79
- const book = makeBook({ title: "成功" });
80
- const failingAdapter: PublisherAdapter = {
81
- id: "fail",
82
- name: "失敗社",
83
- baseUrl: "https://fail.example.com",
84
- search: mockFn(() => Promise.reject(new Error("network error"))),
85
- getDetail: mockFn(),
86
- };
87
- const publishers = [failingAdapter, makeAdapter("ok", [book])];
88
-
89
- const { books, errors } = await searchBooks({ title: "テスト" }, publishers, makeDeps());
90
-
91
- assert.strictEqual(books.length, 1);
92
- assert.strictEqual(books[0].title, "成功");
93
- assert.strictEqual(errors.length, 1);
94
- assert.deepStrictEqual(errors[0], { publisherId: "fail", message: "network error" });
95
- });
96
-
97
- it("全アダプターが失敗した場合は books が空で errors に全件入る", async () => {
98
- const publishers = [
99
- { id: "a", name: "A社", baseUrl: "https://a.example.com", search: mockFn(() => Promise.reject(new Error("err A"))), getDetail: mockFn() },
100
- { id: "b", name: "B社", baseUrl: "https://b.example.com", search: mockFn(() => Promise.reject(new Error("err B"))), getDetail: mockFn() },
101
- ];
102
-
103
- const { books, errors } = await searchBooks({ title: "テスト" }, publishers, makeDeps());
104
-
105
- assert.strictEqual(books.length, 0);
106
- assert.strictEqual(errors.length, 2);
107
- assert.deepStrictEqual(errors.map(e => e.publisherId), ["a", "b"]);
108
- });
109
-
110
- it("Error 以外の例外も文字列化して errors に入れる", async () => {
111
- const publishers = [
112
- { id: "x", name: "X社", baseUrl: "https://x.example.com", search: mockFn(() => Promise.reject("string error")), getDetail: mockFn() },
113
- ];
114
-
115
- const { errors } = await searchBooks({ title: "テスト" }, publishers, makeDeps());
116
-
117
- assert.strictEqual(errors[0].message, "string error");
118
- });
119
-
120
- it("アダプターが0件のとき空配列を返す", async () => {
121
- const { books, errors } = await searchBooks({ title: "テスト" }, [], makeDeps());
122
- assert.deepStrictEqual(books, []);
123
- assert.deepStrictEqual(errors, []);
124
- });
125
-
126
- it("クエリをそのまま各アダプターの search() に渡す", async () => {
127
- const adapter = makeAdapter("a", []);
128
- const query = { title: "TypeScript", author: "山田", limit: 5 };
129
-
130
- await searchBooks(query, [adapter], makeDeps());
131
-
132
- assert.deepStrictEqual(
133
- (adapter.search as ReturnType<typeof mockFn>).mock.calls[0].arguments[0],
134
- query,
135
- );
136
- });
137
- });
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "declaration": true,
12
- "declarationMap": true,
13
- "sourceMap": true
14
- },
15
- "include": ["src/**/*"],
16
- "exclude": ["node_modules", "dist"]
17
- }