@debriefer/sources 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/dist/__tests__/archives/chronicling-america.test.d.ts +8 -0
- package/dist/__tests__/archives/chronicling-america.test.d.ts.map +1 -0
- package/dist/__tests__/archives/chronicling-america.test.js +151 -0
- package/dist/__tests__/archives/chronicling-america.test.js.map +1 -0
- package/dist/__tests__/archives/europeana.test.d.ts +8 -0
- package/dist/__tests__/archives/europeana.test.d.ts.map +1 -0
- package/dist/__tests__/archives/europeana.test.js +200 -0
- package/dist/__tests__/archives/europeana.test.js.map +1 -0
- package/dist/__tests__/archives/internet-archive.test.d.ts +8 -0
- package/dist/__tests__/archives/internet-archive.test.d.ts.map +1 -0
- package/dist/__tests__/archives/internet-archive.test.js +189 -0
- package/dist/__tests__/archives/internet-archive.test.js.map +1 -0
- package/dist/__tests__/archives/trove.test.d.ts +8 -0
- package/dist/__tests__/archives/trove.test.d.ts.map +1 -0
- package/dist/__tests__/archives/trove.test.js +202 -0
- package/dist/__tests__/archives/trove.test.js.map +1 -0
- package/dist/__tests__/books/google-books.test.d.ts +8 -0
- package/dist/__tests__/books/google-books.test.d.ts.map +1 -0
- package/dist/__tests__/books/google-books.test.js +221 -0
- package/dist/__tests__/books/google-books.test.js.map +1 -0
- package/dist/__tests__/books/open-library.test.d.ts +8 -0
- package/dist/__tests__/books/open-library.test.d.ts.map +1 -0
- package/dist/__tests__/books/open-library.test.js +159 -0
- package/dist/__tests__/books/open-library.test.js.map +1 -0
- package/dist/__tests__/news/guardian.test.d.ts +9 -0
- package/dist/__tests__/news/guardian.test.d.ts.map +1 -0
- package/dist/__tests__/news/guardian.test.js +224 -0
- package/dist/__tests__/news/guardian.test.js.map +1 -0
- package/dist/__tests__/news/nytimes.test.d.ts +9 -0
- package/dist/__tests__/news/nytimes.test.d.ts.map +1 -0
- package/dist/__tests__/news/nytimes.test.js +271 -0
- package/dist/__tests__/news/nytimes.test.js.map +1 -0
- package/dist/__tests__/news/site-search-source.test.d.ts +9 -0
- package/dist/__tests__/news/site-search-source.test.d.ts.map +1 -0
- package/dist/__tests__/news/site-search-source.test.js +342 -0
- package/dist/__tests__/news/site-search-source.test.js.map +1 -0
- package/dist/__tests__/obituary/find-a-grave.test.d.ts +8 -0
- package/dist/__tests__/obituary/find-a-grave.test.d.ts.map +1 -0
- package/dist/__tests__/obituary/find-a-grave.test.js +238 -0
- package/dist/__tests__/obituary/find-a-grave.test.js.map +1 -0
- package/dist/__tests__/shared/duckduckgo-search.test.d.ts +9 -0
- package/dist/__tests__/shared/duckduckgo-search.test.d.ts.map +1 -0
- package/dist/__tests__/shared/duckduckgo-search.test.js +218 -0
- package/dist/__tests__/shared/duckduckgo-search.test.js.map +1 -0
- package/dist/__tests__/shared/fetch-page.test.d.ts +9 -0
- package/dist/__tests__/shared/fetch-page.test.d.ts.map +1 -0
- package/dist/__tests__/shared/fetch-page.test.js +281 -0
- package/dist/__tests__/shared/fetch-page.test.js.map +1 -0
- package/dist/__tests__/shared/html-utils.test.d.ts +2 -0
- package/dist/__tests__/shared/html-utils.test.d.ts.map +1 -0
- package/dist/__tests__/shared/html-utils.test.js +169 -0
- package/dist/__tests__/shared/html-utils.test.js.map +1 -0
- package/dist/__tests__/shared/readability-extract.test.d.ts +2 -0
- package/dist/__tests__/shared/readability-extract.test.d.ts.map +1 -0
- package/dist/__tests__/shared/readability-extract.test.js +107 -0
- package/dist/__tests__/shared/readability-extract.test.js.map +1 -0
- package/dist/__tests__/shared/sanitize-text.test.d.ts +2 -0
- package/dist/__tests__/shared/sanitize-text.test.d.ts.map +1 -0
- package/dist/__tests__/shared/sanitize-text.test.js +77 -0
- package/dist/__tests__/shared/sanitize-text.test.js.map +1 -0
- package/dist/__tests__/shared/search-utils.test.d.ts +2 -0
- package/dist/__tests__/shared/search-utils.test.d.ts.map +1 -0
- package/dist/__tests__/shared/search-utils.test.js +26 -0
- package/dist/__tests__/shared/search-utils.test.js.map +1 -0
- package/dist/__tests__/structured/wikidata.test.d.ts +9 -0
- package/dist/__tests__/structured/wikidata.test.d.ts.map +1 -0
- package/dist/__tests__/structured/wikidata.test.js +509 -0
- package/dist/__tests__/structured/wikidata.test.js.map +1 -0
- package/dist/__tests__/structured/wikipedia.test.d.ts +9 -0
- package/dist/__tests__/structured/wikipedia.test.d.ts.map +1 -0
- package/dist/__tests__/structured/wikipedia.test.js +643 -0
- package/dist/__tests__/structured/wikipedia.test.js.map +1 -0
- package/dist/__tests__/web-search/base.test.d.ts +9 -0
- package/dist/__tests__/web-search/base.test.d.ts.map +1 -0
- package/dist/__tests__/web-search/base.test.js +622 -0
- package/dist/__tests__/web-search/base.test.js.map +1 -0
- package/dist/__tests__/web-search/bing.test.d.ts +10 -0
- package/dist/__tests__/web-search/bing.test.d.ts.map +1 -0
- package/dist/__tests__/web-search/bing.test.js +277 -0
- package/dist/__tests__/web-search/bing.test.js.map +1 -0
- package/dist/__tests__/web-search/brave.test.d.ts +10 -0
- package/dist/__tests__/web-search/brave.test.d.ts.map +1 -0
- package/dist/__tests__/web-search/brave.test.js +264 -0
- package/dist/__tests__/web-search/brave.test.js.map +1 -0
- package/dist/__tests__/web-search/duckduckgo.test.d.ts +10 -0
- package/dist/__tests__/web-search/duckduckgo.test.d.ts.map +1 -0
- package/dist/__tests__/web-search/duckduckgo.test.js +107 -0
- package/dist/__tests__/web-search/duckduckgo.test.js.map +1 -0
- package/dist/__tests__/web-search/google.test.d.ts +9 -0
- package/dist/__tests__/web-search/google.test.d.ts.map +1 -0
- package/dist/__tests__/web-search/google.test.js +189 -0
- package/dist/__tests__/web-search/google.test.js.map +1 -0
- package/dist/archives/chronicling-america.d.ts +33 -0
- package/dist/archives/chronicling-america.d.ts.map +1 -0
- package/dist/archives/chronicling-america.js +85 -0
- package/dist/archives/chronicling-america.js.map +1 -0
- package/dist/archives/europeana.d.ts +37 -0
- package/dist/archives/europeana.d.ts.map +1 -0
- package/dist/archives/europeana.js +92 -0
- package/dist/archives/europeana.js.map +1 -0
- package/dist/archives/internet-archive.d.ts +32 -0
- package/dist/archives/internet-archive.d.ts.map +1 -0
- package/dist/archives/internet-archive.js +90 -0
- package/dist/archives/internet-archive.js.map +1 -0
- package/dist/archives/trove.d.ts +37 -0
- package/dist/archives/trove.d.ts.map +1 -0
- package/dist/archives/trove.js +97 -0
- package/dist/archives/trove.js.map +1 -0
- package/dist/books/google-books.d.ts +48 -0
- package/dist/books/google-books.d.ts.map +1 -0
- package/dist/books/google-books.js +111 -0
- package/dist/books/google-books.js.map +1 -0
- package/dist/books/open-library.d.ts +44 -0
- package/dist/books/open-library.d.ts.map +1 -0
- package/dist/books/open-library.js +103 -0
- package/dist/books/open-library.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/news/guardian.d.ts +51 -0
- package/dist/news/guardian.d.ts.map +1 -0
- package/dist/news/guardian.js +131 -0
- package/dist/news/guardian.js.map +1 -0
- package/dist/news/nytimes.d.ts +27 -0
- package/dist/news/nytimes.d.ts.map +1 -0
- package/dist/news/nytimes.js +104 -0
- package/dist/news/nytimes.js.map +1 -0
- package/dist/news/site-search-source.d.ts +89 -0
- package/dist/news/site-search-source.d.ts.map +1 -0
- package/dist/news/site-search-source.js +182 -0
- package/dist/news/site-search-source.js.map +1 -0
- package/dist/news/sources.d.ts +52 -0
- package/dist/news/sources.d.ts.map +1 -0
- package/dist/news/sources.js +276 -0
- package/dist/news/sources.js.map +1 -0
- package/dist/obituary/find-a-grave.d.ts +43 -0
- package/dist/obituary/find-a-grave.d.ts.map +1 -0
- package/dist/obituary/find-a-grave.js +173 -0
- package/dist/obituary/find-a-grave.js.map +1 -0
- package/dist/shared/duckduckgo-search.d.ts +86 -0
- package/dist/shared/duckduckgo-search.d.ts.map +1 -0
- package/dist/shared/duckduckgo-search.js +218 -0
- package/dist/shared/duckduckgo-search.js.map +1 -0
- package/dist/shared/fetch-page.d.ts +50 -0
- package/dist/shared/fetch-page.d.ts.map +1 -0
- package/dist/shared/fetch-page.js +212 -0
- package/dist/shared/fetch-page.js.map +1 -0
- package/dist/shared/html-utils.d.ts +99 -0
- package/dist/shared/html-utils.d.ts.map +1 -0
- package/dist/shared/html-utils.js +246 -0
- package/dist/shared/html-utils.js.map +1 -0
- package/dist/shared/readability-extract.d.ts +33 -0
- package/dist/shared/readability-extract.d.ts.map +1 -0
- package/dist/shared/readability-extract.js +45 -0
- package/dist/shared/readability-extract.js.map +1 -0
- package/dist/shared/sanitize-text.d.ts +24 -0
- package/dist/shared/sanitize-text.d.ts.map +1 -0
- package/dist/shared/sanitize-text.js +49 -0
- package/dist/shared/sanitize-text.js.map +1 -0
- package/dist/shared/search-utils.d.ts +18 -0
- package/dist/shared/search-utils.d.ts.map +1 -0
- package/dist/shared/search-utils.js +20 -0
- package/dist/shared/search-utils.js.map +1 -0
- package/dist/structured/wikidata.d.ts +128 -0
- package/dist/structured/wikidata.d.ts.map +1 -0
- package/dist/structured/wikidata.js +361 -0
- package/dist/structured/wikidata.js.map +1 -0
- package/dist/structured/wikipedia.d.ts +184 -0
- package/dist/structured/wikipedia.d.ts.map +1 -0
- package/dist/structured/wikipedia.js +275 -0
- package/dist/structured/wikipedia.js.map +1 -0
- package/dist/web-search/base.d.ts +128 -0
- package/dist/web-search/base.d.ts.map +1 -0
- package/dist/web-search/base.js +251 -0
- package/dist/web-search/base.js.map +1 -0
- package/dist/web-search/bing.d.ts +21 -0
- package/dist/web-search/bing.d.ts.map +1 -0
- package/dist/web-search/bing.js +53 -0
- package/dist/web-search/bing.js.map +1 -0
- package/dist/web-search/brave.d.ts +21 -0
- package/dist/web-search/brave.d.ts.map +1 -0
- package/dist/web-search/brave.js +56 -0
- package/dist/web-search/brave.js.map +1 -0
- package/dist/web-search/duckduckgo.d.ts +15 -0
- package/dist/web-search/duckduckgo.d.ts.map +1 -0
- package/dist/web-search/duckduckgo.js +21 -0
- package/dist/web-search/duckduckgo.js.map +1 -0
- package/dist/web-search/google.d.ts +24 -0
- package/dist/web-search/google.d.ts.map +1 -0
- package/dist/web-search/google.js +48 -0
- package/dist/web-search/google.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for GoogleBooksSource.
|
|
3
|
+
*
|
|
4
|
+
* Mocks the global fetch API and sanitizeSourceText to isolate
|
|
5
|
+
* Google Books-specific logic.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
8
|
+
import { ReliabilityTier } from "@debriefer/core";
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Mocks — must be declared before importing the module under test
|
|
11
|
+
// ============================================================================
|
|
12
|
+
const mockSanitize = vi.fn();
|
|
13
|
+
const mockDecodeEntities = vi.fn();
|
|
14
|
+
vi.mock("../../shared/sanitize-text.js", () => ({
|
|
15
|
+
sanitizeSourceText: (...args) => mockSanitize(...args),
|
|
16
|
+
}));
|
|
17
|
+
vi.mock("../../shared/html-utils.js", () => ({
|
|
18
|
+
decodeHtmlEntities: (...args) => mockDecodeEntities(...args),
|
|
19
|
+
}));
|
|
20
|
+
import { GoogleBooksSource, googleBooks } from "../../books/google-books.js";
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Helpers
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/** Standard test subject. */
|
|
25
|
+
const subject = { id: 1, name: "John Wayne" };
|
|
26
|
+
/** Standard abort signal for tests. */
|
|
27
|
+
const signal = AbortSignal.timeout(5000);
|
|
28
|
+
/** Build a mock Google Books API response. */
|
|
29
|
+
function makeGoogleBooksResponse(items) {
|
|
30
|
+
return {
|
|
31
|
+
ok: true,
|
|
32
|
+
status: 200,
|
|
33
|
+
statusText: "OK",
|
|
34
|
+
json: async () => ({
|
|
35
|
+
totalItems: items.length,
|
|
36
|
+
items: items.length > 0 ? items : undefined,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Description long enough to pass the 100-char minimum. */
|
|
41
|
+
const LONG_DESCRIPTION = "A".repeat(150);
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Lifecycle
|
|
44
|
+
// ============================================================================
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
mockSanitize.mockReset();
|
|
47
|
+
mockDecodeEntities.mockReset();
|
|
48
|
+
// Default: pass through
|
|
49
|
+
mockSanitize.mockImplementation((text) => text);
|
|
50
|
+
mockDecodeEntities.mockImplementation((text) => text);
|
|
51
|
+
vi.stubGlobal("fetch", vi.fn());
|
|
52
|
+
});
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
vi.restoreAllMocks();
|
|
55
|
+
vi.unstubAllGlobals();
|
|
56
|
+
});
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Tests
|
|
59
|
+
// ============================================================================
|
|
60
|
+
describe("GoogleBooksSource", () => {
|
|
61
|
+
describe("metadata", () => {
|
|
62
|
+
it("has correct name, type, tier, domain, isFree, and cost", () => {
|
|
63
|
+
const source = new GoogleBooksSource({ apiKey: "test-key" });
|
|
64
|
+
expect(source.name).toBe("Google Books");
|
|
65
|
+
expect(source.type).toBe("google-books");
|
|
66
|
+
expect(source.reliabilityTier).toBe(ReliabilityTier.SECONDARY_COMPILATION);
|
|
67
|
+
expect(source.domain).toBe("www.googleapis.com");
|
|
68
|
+
expect(source.isFree).toBe(true);
|
|
69
|
+
expect(source.estimatedCostPerQuery).toBe(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
// ==========================================================================
|
|
73
|
+
// isAvailable
|
|
74
|
+
// ==========================================================================
|
|
75
|
+
describe("isAvailable", () => {
|
|
76
|
+
it("returns true when apiKey is provided", () => {
|
|
77
|
+
const source = new GoogleBooksSource({ apiKey: "test-key" });
|
|
78
|
+
expect(source.isAvailable()).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
it("returns false when no apiKey is provided and env var is unset", () => {
|
|
81
|
+
const original = process.env.GOOGLE_BOOKS_API_KEY;
|
|
82
|
+
delete process.env.GOOGLE_BOOKS_API_KEY;
|
|
83
|
+
const source = new GoogleBooksSource();
|
|
84
|
+
expect(source.isAvailable()).toBe(false);
|
|
85
|
+
if (original !== undefined) {
|
|
86
|
+
process.env.GOOGLE_BOOKS_API_KEY = original;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
// ==========================================================================
|
|
91
|
+
// API call format
|
|
92
|
+
// ==========================================================================
|
|
93
|
+
describe("API call format", () => {
|
|
94
|
+
it("builds correct URL with all expected query params", async () => {
|
|
95
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGoogleBooksResponse([]));
|
|
96
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
97
|
+
const source = new GoogleBooksSource({ apiKey: "my-api-key" });
|
|
98
|
+
await source.lookup(subject, signal);
|
|
99
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
100
|
+
const [callUrl, callOptions] = mockFetch.mock.calls[0];
|
|
101
|
+
const parsed = new URL(callUrl);
|
|
102
|
+
expect(parsed.origin + parsed.pathname).toBe("https://www.googleapis.com/books/v1/volumes");
|
|
103
|
+
expect(parsed.searchParams.get("key")).toBe("my-api-key");
|
|
104
|
+
expect(parsed.searchParams.get("q")).toBe('"John Wayne" biography');
|
|
105
|
+
expect(parsed.searchParams.get("maxResults")).toBe("5");
|
|
106
|
+
expect(callOptions.signal).toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
// ==========================================================================
|
|
110
|
+
// Response parsing
|
|
111
|
+
// ==========================================================================
|
|
112
|
+
describe("response parsing", () => {
|
|
113
|
+
it("returns finding with metadata from first item with enough text", async () => {
|
|
114
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGoogleBooksResponse([
|
|
115
|
+
{
|
|
116
|
+
volumeInfo: {
|
|
117
|
+
title: "John Wayne: The Life",
|
|
118
|
+
description: LONG_DESCRIPTION,
|
|
119
|
+
authors: ["Author One"],
|
|
120
|
+
publishedDate: "2020-01-01",
|
|
121
|
+
infoLink: "https://books.google.com/books?id=abc123",
|
|
122
|
+
},
|
|
123
|
+
searchInfo: {
|
|
124
|
+
textSnippet: "A <b>bold</b> snippet about John Wayne's life.",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
]));
|
|
128
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
129
|
+
const source = new GoogleBooksSource({ apiKey: "test-key" });
|
|
130
|
+
const result = await source.lookup(subject, signal);
|
|
131
|
+
expect(result).not.toBeNull();
|
|
132
|
+
expect(result.publication).toBe("Google Books");
|
|
133
|
+
expect(result.confidence).toBe(-1);
|
|
134
|
+
expect(result.costUsd).toBe(0);
|
|
135
|
+
expect(result.url).toBe("https://books.google.com/books?id=abc123");
|
|
136
|
+
expect(result.metadata?.title).toBe("John Wayne: The Life");
|
|
137
|
+
expect(result.metadata?.authors).toEqual(["Author One"]);
|
|
138
|
+
expect(result.metadata?.publishedDate).toBe("2020-01-01");
|
|
139
|
+
});
|
|
140
|
+
it("strips HTML tags from textSnippet and decodes entities", async () => {
|
|
141
|
+
const snippet = "A <b>bold</b> snippet & more";
|
|
142
|
+
const decoded = "A bold snippet & more";
|
|
143
|
+
mockDecodeEntities.mockReturnValue(decoded);
|
|
144
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGoogleBooksResponse([
|
|
145
|
+
{
|
|
146
|
+
volumeInfo: {
|
|
147
|
+
title: "Test Book",
|
|
148
|
+
description: LONG_DESCRIPTION,
|
|
149
|
+
infoLink: "https://books.google.com/books?id=xyz",
|
|
150
|
+
},
|
|
151
|
+
searchInfo: { textSnippet: snippet },
|
|
152
|
+
},
|
|
153
|
+
]));
|
|
154
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
155
|
+
const source = new GoogleBooksSource({ apiKey: "test-key" });
|
|
156
|
+
await source.lookup(subject, signal);
|
|
157
|
+
// decodeHtmlEntities should have been called with tag-stripped snippet
|
|
158
|
+
expect(mockDecodeEntities).toHaveBeenCalledWith("A bold snippet & more");
|
|
159
|
+
// sanitizeSourceText should have been called with combined text
|
|
160
|
+
expect(mockSanitize).toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
it("skips items with combined text shorter than 100 chars", async () => {
|
|
163
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGoogleBooksResponse([
|
|
164
|
+
{
|
|
165
|
+
volumeInfo: {
|
|
166
|
+
title: "Short Book",
|
|
167
|
+
description: "Too short",
|
|
168
|
+
infoLink: "https://books.google.com/books?id=short",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
volumeInfo: {
|
|
173
|
+
title: "Long Book",
|
|
174
|
+
description: LONG_DESCRIPTION,
|
|
175
|
+
infoLink: "https://books.google.com/books?id=long",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
]));
|
|
179
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
180
|
+
const source = new GoogleBooksSource({ apiKey: "test-key" });
|
|
181
|
+
const result = await source.lookup(subject, signal);
|
|
182
|
+
expect(result).not.toBeNull();
|
|
183
|
+
expect(result.metadata?.title).toBe("Long Book");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
// ==========================================================================
|
|
187
|
+
// Null returns
|
|
188
|
+
// ==========================================================================
|
|
189
|
+
describe("null returns", () => {
|
|
190
|
+
it("returns null when no items in response", async () => {
|
|
191
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGoogleBooksResponse([]));
|
|
192
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
193
|
+
const source = new GoogleBooksSource({ apiKey: "test-key" });
|
|
194
|
+
const result = await source.lookup(subject, signal);
|
|
195
|
+
expect(result).toBeNull();
|
|
196
|
+
});
|
|
197
|
+
it("returns null on API error (caught by base class)", async () => {
|
|
198
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
199
|
+
ok: false,
|
|
200
|
+
status: 403,
|
|
201
|
+
statusText: "Forbidden",
|
|
202
|
+
json: async () => ({}),
|
|
203
|
+
});
|
|
204
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
205
|
+
const source = new GoogleBooksSource({ apiKey: "test-key" });
|
|
206
|
+
const result = await source.lookup(subject, signal);
|
|
207
|
+
expect(result).toBeNull();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
// ==========================================================================
|
|
211
|
+
// Factory function
|
|
212
|
+
// ==========================================================================
|
|
213
|
+
describe("factory", () => {
|
|
214
|
+
it("returns a GoogleBooksSource instance", () => {
|
|
215
|
+
const source = googleBooks({ apiKey: "test-key" });
|
|
216
|
+
expect(source).toBeInstanceOf(GoogleBooksSource);
|
|
217
|
+
expect(source.name).toBe("Google Books");
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
//# sourceMappingURL=google-books.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-books.test.js","sourceRoot":"","sources":["../../../src/__tests__/books/google-books.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-library.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/books/open-library.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for OpenLibrarySource.
|
|
3
|
+
*
|
|
4
|
+
* Mocks the global fetch API. Open Library requires no API key
|
|
5
|
+
* and returns structured JSON — no HTML parsing needed.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
8
|
+
import { ReliabilityTier } from "@debriefer/core";
|
|
9
|
+
import { OpenLibrarySource, openLibrary } from "../../books/open-library.js";
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Helpers
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/** Standard test subject. */
|
|
14
|
+
const subject = { id: 1, name: "John Wayne" };
|
|
15
|
+
/** Standard abort signal for tests. */
|
|
16
|
+
const signal = AbortSignal.timeout(5000);
|
|
17
|
+
/** Build a mock Open Library search response. */
|
|
18
|
+
function makeOpenLibraryResponse(docs) {
|
|
19
|
+
return {
|
|
20
|
+
ok: true,
|
|
21
|
+
status: 200,
|
|
22
|
+
statusText: "OK",
|
|
23
|
+
json: async () => ({
|
|
24
|
+
numFound: docs.length,
|
|
25
|
+
docs,
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Lifecycle
|
|
31
|
+
// ============================================================================
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.stubGlobal("fetch", vi.fn());
|
|
34
|
+
});
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
vi.restoreAllMocks();
|
|
37
|
+
vi.unstubAllGlobals();
|
|
38
|
+
});
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Tests
|
|
41
|
+
// ============================================================================
|
|
42
|
+
describe("OpenLibrarySource", () => {
|
|
43
|
+
describe("metadata", () => {
|
|
44
|
+
it("has correct name, type, tier, domain, isFree, and cost", () => {
|
|
45
|
+
const source = new OpenLibrarySource();
|
|
46
|
+
expect(source.name).toBe("Open Library");
|
|
47
|
+
expect(source.type).toBe("open-library");
|
|
48
|
+
expect(source.reliabilityTier).toBe(ReliabilityTier.SECONDARY_COMPILATION);
|
|
49
|
+
expect(source.domain).toBe("openlibrary.org");
|
|
50
|
+
expect(source.isFree).toBe(true);
|
|
51
|
+
expect(source.estimatedCostPerQuery).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
// ==========================================================================
|
|
55
|
+
// isAvailable
|
|
56
|
+
// ==========================================================================
|
|
57
|
+
describe("isAvailable", () => {
|
|
58
|
+
it("returns true always (no API key required)", () => {
|
|
59
|
+
const source = new OpenLibrarySource();
|
|
60
|
+
expect(source.isAvailable()).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// ==========================================================================
|
|
64
|
+
// API call format
|
|
65
|
+
// ==========================================================================
|
|
66
|
+
describe("API call format", () => {
|
|
67
|
+
it("builds correct URL with expected query params", async () => {
|
|
68
|
+
const mockFetch = vi.fn().mockResolvedValue(makeOpenLibraryResponse([]));
|
|
69
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
70
|
+
const source = new OpenLibrarySource();
|
|
71
|
+
await source.lookup(subject, signal);
|
|
72
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
73
|
+
const [callUrl, callOptions] = mockFetch.mock.calls[0];
|
|
74
|
+
const parsed = new URL(callUrl);
|
|
75
|
+
expect(parsed.origin + parsed.pathname).toBe("https://openlibrary.org/search.json");
|
|
76
|
+
expect(parsed.searchParams.get("q")).toBe('"John Wayne" biography');
|
|
77
|
+
expect(parsed.searchParams.get("limit")).toBe("5");
|
|
78
|
+
expect(callOptions.signal).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// ==========================================================================
|
|
82
|
+
// Response parsing
|
|
83
|
+
// ==========================================================================
|
|
84
|
+
describe("response parsing", () => {
|
|
85
|
+
it("returns finding with metadata from first doc with a title", async () => {
|
|
86
|
+
const mockFetch = vi.fn().mockResolvedValue(makeOpenLibraryResponse([
|
|
87
|
+
{
|
|
88
|
+
key: "/works/OL12345W",
|
|
89
|
+
title: "John Wayne: American Legend",
|
|
90
|
+
author_name: ["Jane Smith"],
|
|
91
|
+
first_publish_year: 1998,
|
|
92
|
+
ia: ["johnwaynelegend00smit"],
|
|
93
|
+
},
|
|
94
|
+
]));
|
|
95
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
96
|
+
const source = new OpenLibrarySource();
|
|
97
|
+
const result = await source.lookup(subject, signal);
|
|
98
|
+
expect(result).not.toBeNull();
|
|
99
|
+
expect(result.text).toBe("John Wayne: American Legend by Jane Smith (1998)");
|
|
100
|
+
expect(result.publication).toBe("Open Library");
|
|
101
|
+
expect(result.confidence).toBe(-1);
|
|
102
|
+
expect(result.costUsd).toBe(0);
|
|
103
|
+
expect(result.url).toBe("https://openlibrary.org/works/OL12345W");
|
|
104
|
+
expect(result.metadata?.title).toBe("John Wayne: American Legend");
|
|
105
|
+
expect(result.metadata?.authors).toEqual(["Jane Smith"]);
|
|
106
|
+
expect(result.metadata?.firstPublishYear).toBe(1998);
|
|
107
|
+
});
|
|
108
|
+
it("handles docs with multiple authors", async () => {
|
|
109
|
+
const mockFetch = vi.fn().mockResolvedValue(makeOpenLibraryResponse([
|
|
110
|
+
{
|
|
111
|
+
key: "/works/OL999W",
|
|
112
|
+
title: "Dual Authors Book",
|
|
113
|
+
author_name: ["Author A", "Author B"],
|
|
114
|
+
first_publish_year: 2005,
|
|
115
|
+
},
|
|
116
|
+
]));
|
|
117
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
118
|
+
const source = new OpenLibrarySource();
|
|
119
|
+
const result = await source.lookup(subject, signal);
|
|
120
|
+
expect(result).not.toBeNull();
|
|
121
|
+
expect(result.text).toBe("Dual Authors Book by Author A, Author B (2005)");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
// ==========================================================================
|
|
125
|
+
// Null returns
|
|
126
|
+
// ==========================================================================
|
|
127
|
+
describe("null returns", () => {
|
|
128
|
+
it("returns null when no docs in response", async () => {
|
|
129
|
+
const mockFetch = vi.fn().mockResolvedValue(makeOpenLibraryResponse([]));
|
|
130
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
131
|
+
const source = new OpenLibrarySource();
|
|
132
|
+
const result = await source.lookup(subject, signal);
|
|
133
|
+
expect(result).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
it("returns null on API error (caught by base class)", async () => {
|
|
136
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
137
|
+
ok: false,
|
|
138
|
+
status: 500,
|
|
139
|
+
statusText: "Internal Server Error",
|
|
140
|
+
json: async () => ({}),
|
|
141
|
+
});
|
|
142
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
143
|
+
const source = new OpenLibrarySource();
|
|
144
|
+
const result = await source.lookup(subject, signal);
|
|
145
|
+
expect(result).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
// ==========================================================================
|
|
149
|
+
// Factory function
|
|
150
|
+
// ==========================================================================
|
|
151
|
+
describe("factory", () => {
|
|
152
|
+
it("returns an OpenLibrarySource instance", () => {
|
|
153
|
+
const source = openLibrary();
|
|
154
|
+
expect(source).toBeInstanceOf(OpenLibrarySource);
|
|
155
|
+
expect(source.name).toBe("Open Library");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
//# sourceMappingURL=open-library.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-library.test.js","sourceRoot":"","sources":["../../../src/__tests__/books/open-library.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for GuardianSource.
|
|
3
|
+
*
|
|
4
|
+
* Mocks the global fetch API. Does NOT mock pipeline utilities
|
|
5
|
+
* (Guardian uses its own API, not fetchPage/extractArticleContent).
|
|
6
|
+
* sanitizeSourceText is mocked to isolate Guardian-specific logic.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=guardian.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardian.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/news/guardian.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for GuardianSource.
|
|
3
|
+
*
|
|
4
|
+
* Mocks the global fetch API. Does NOT mock pipeline utilities
|
|
5
|
+
* (Guardian uses its own API, not fetchPage/extractArticleContent).
|
|
6
|
+
* sanitizeSourceText is mocked to isolate Guardian-specific logic.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
9
|
+
import { ReliabilityTier } from "@debriefer/core";
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Mocks — must be declared before importing the module under test
|
|
12
|
+
// ============================================================================
|
|
13
|
+
const mockSanitize = vi.fn();
|
|
14
|
+
vi.mock("../../shared/sanitize-text.js", () => ({
|
|
15
|
+
sanitizeSourceText: (...args) => mockSanitize(...args),
|
|
16
|
+
}));
|
|
17
|
+
import { GuardianSource, guardian } from "../../news/guardian.js";
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Helpers
|
|
20
|
+
// ============================================================================
|
|
21
|
+
/** Standard test subject. */
|
|
22
|
+
const subject = { id: 1, name: "John Wayne" };
|
|
23
|
+
/** Standard abort signal for tests. */
|
|
24
|
+
const signal = AbortSignal.timeout(5000);
|
|
25
|
+
/** Build a mock Guardian API response. */
|
|
26
|
+
function makeGuardianResponse(results) {
|
|
27
|
+
return {
|
|
28
|
+
ok: true,
|
|
29
|
+
status: 200,
|
|
30
|
+
statusText: "OK",
|
|
31
|
+
json: async () => ({
|
|
32
|
+
response: {
|
|
33
|
+
status: "ok",
|
|
34
|
+
results,
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Body text long enough to pass the 200-char minimum. */
|
|
40
|
+
const LONG_TEXT = "A".repeat(300);
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Lifecycle
|
|
43
|
+
// ============================================================================
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
mockSanitize.mockReset();
|
|
46
|
+
vi.stubGlobal("fetch", vi.fn());
|
|
47
|
+
});
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
vi.restoreAllMocks();
|
|
50
|
+
vi.unstubAllGlobals();
|
|
51
|
+
});
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Metadata
|
|
54
|
+
// ============================================================================
|
|
55
|
+
describe("GuardianSource", () => {
|
|
56
|
+
describe("metadata", () => {
|
|
57
|
+
it("has correct name, type, tier, domain, isFree, and cost", () => {
|
|
58
|
+
const source = new GuardianSource({ apiKey: "test-key" });
|
|
59
|
+
expect(source.name).toBe("The Guardian");
|
|
60
|
+
expect(source.type).toBe("guardian");
|
|
61
|
+
expect(source.reliabilityTier).toBe(ReliabilityTier.TIER_1_NEWS);
|
|
62
|
+
expect(source.domain).toBe("content.guardianapis.com");
|
|
63
|
+
expect(source.isFree).toBe(true);
|
|
64
|
+
expect(source.estimatedCostPerQuery).toBe(0);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
// ==========================================================================
|
|
68
|
+
// isAvailable
|
|
69
|
+
// ==========================================================================
|
|
70
|
+
describe("isAvailable", () => {
|
|
71
|
+
it("returns true when apiKey is provided", () => {
|
|
72
|
+
const source = new GuardianSource({ apiKey: "test-key" });
|
|
73
|
+
expect(source.isAvailable()).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it("returns false when no apiKey is provided and env var is unset", () => {
|
|
76
|
+
const original = process.env.GUARDIAN_API_KEY;
|
|
77
|
+
delete process.env.GUARDIAN_API_KEY;
|
|
78
|
+
const source = new GuardianSource();
|
|
79
|
+
expect(source.isAvailable()).toBe(false);
|
|
80
|
+
// Restore
|
|
81
|
+
if (original !== undefined) {
|
|
82
|
+
process.env.GUARDIAN_API_KEY = original;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
// ==========================================================================
|
|
87
|
+
// API call format
|
|
88
|
+
// ==========================================================================
|
|
89
|
+
describe("API call format", () => {
|
|
90
|
+
it("builds correct URL with all expected query params", async () => {
|
|
91
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGuardianResponse([]));
|
|
92
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
93
|
+
const source = new GuardianSource({ apiKey: "my-api-key" });
|
|
94
|
+
await source.lookup(subject, signal);
|
|
95
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
96
|
+
const [callUrl, callOptions] = mockFetch.mock.calls[0];
|
|
97
|
+
const parsed = new URL(callUrl);
|
|
98
|
+
expect(parsed.origin + parsed.pathname).toBe("https://content.guardianapis.com/search");
|
|
99
|
+
expect(parsed.searchParams.get("api-key")).toBe("my-api-key");
|
|
100
|
+
expect(parsed.searchParams.get("q")).toContain('"John Wayne"');
|
|
101
|
+
expect(parsed.searchParams.get("show-fields")).toBe("bodyText,standfirst,trailText");
|
|
102
|
+
expect(parsed.searchParams.get("page-size")).toBe("10");
|
|
103
|
+
expect(parsed.searchParams.get("order-by")).toBe("relevance");
|
|
104
|
+
expect(callOptions.signal).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// ==========================================================================
|
|
108
|
+
// Response parsing — article selection
|
|
109
|
+
// ==========================================================================
|
|
110
|
+
describe("article selection", () => {
|
|
111
|
+
it("picks article with bio keyword in title", async () => {
|
|
112
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGuardianResponse([
|
|
113
|
+
{
|
|
114
|
+
webTitle: "John Wayne filmography roundup",
|
|
115
|
+
webUrl: "https://theguardian.com/film/roundup",
|
|
116
|
+
fields: { bodyText: LONG_TEXT },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
webTitle: "John Wayne profile: the man behind the legend",
|
|
120
|
+
webUrl: "https://theguardian.com/film/profile",
|
|
121
|
+
fields: { bodyText: LONG_TEXT },
|
|
122
|
+
},
|
|
123
|
+
]));
|
|
124
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
125
|
+
mockSanitize.mockReturnValue(LONG_TEXT);
|
|
126
|
+
const source = new GuardianSource({ apiKey: "test-key" });
|
|
127
|
+
const result = await source.lookup(subject, signal);
|
|
128
|
+
expect(result).not.toBeNull();
|
|
129
|
+
expect(result.url).toBe("https://theguardian.com/film/profile");
|
|
130
|
+
expect(result.metadata?.title).toBe("John Wayne profile: the man behind the legend");
|
|
131
|
+
});
|
|
132
|
+
it("falls back to first result when no keyword match", async () => {
|
|
133
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGuardianResponse([
|
|
134
|
+
{
|
|
135
|
+
webTitle: "John Wayne in new film role",
|
|
136
|
+
webUrl: "https://theguardian.com/film/new-role",
|
|
137
|
+
fields: { bodyText: LONG_TEXT },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
webTitle: "John Wayne at the Oscars",
|
|
141
|
+
webUrl: "https://theguardian.com/film/oscars",
|
|
142
|
+
fields: { bodyText: LONG_TEXT },
|
|
143
|
+
},
|
|
144
|
+
]));
|
|
145
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
146
|
+
mockSanitize.mockReturnValue(LONG_TEXT);
|
|
147
|
+
const source = new GuardianSource({ apiKey: "test-key" });
|
|
148
|
+
const result = await source.lookup(subject, signal);
|
|
149
|
+
expect(result).not.toBeNull();
|
|
150
|
+
expect(result.url).toBe("https://theguardian.com/film/new-role");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
// ==========================================================================
|
|
154
|
+
// Response parsing — body text
|
|
155
|
+
// ==========================================================================
|
|
156
|
+
describe("body text extraction", () => {
|
|
157
|
+
it("returns bodyText as source text", async () => {
|
|
158
|
+
const bodyText = "B".repeat(300);
|
|
159
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGuardianResponse([
|
|
160
|
+
{
|
|
161
|
+
webTitle: "John Wayne profile",
|
|
162
|
+
webUrl: "https://theguardian.com/film/profile",
|
|
163
|
+
fields: { bodyText },
|
|
164
|
+
},
|
|
165
|
+
]));
|
|
166
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
167
|
+
mockSanitize.mockReturnValue(bodyText);
|
|
168
|
+
const source = new GuardianSource({ apiKey: "test-key" });
|
|
169
|
+
const result = await source.lookup(subject, signal);
|
|
170
|
+
expect(result).not.toBeNull();
|
|
171
|
+
expect(result.text).toBe(bodyText);
|
|
172
|
+
expect(result.confidence).toBe(-1);
|
|
173
|
+
expect(result.costUsd).toBe(0);
|
|
174
|
+
expect(result.publication).toBe("The Guardian");
|
|
175
|
+
expect(mockSanitize).toHaveBeenCalledWith(bodyText);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
// ==========================================================================
|
|
179
|
+
// Null returns
|
|
180
|
+
// ==========================================================================
|
|
181
|
+
describe("null returns", () => {
|
|
182
|
+
it("returns null when no results", async () => {
|
|
183
|
+
const mockFetch = vi.fn().mockResolvedValue(makeGuardianResponse([]));
|
|
184
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
185
|
+
const source = new GuardianSource({ apiKey: "test-key" });
|
|
186
|
+
const result = await source.lookup(subject, signal);
|
|
187
|
+
expect(result).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
it("returns null on API error (caught by base class)", async () => {
|
|
190
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
191
|
+
ok: false,
|
|
192
|
+
status: 403,
|
|
193
|
+
statusText: "Forbidden",
|
|
194
|
+
json: async () => ({}),
|
|
195
|
+
});
|
|
196
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
197
|
+
const source = new GuardianSource({ apiKey: "test-key" });
|
|
198
|
+
// BaseResearchSource.lookup() catches errors from fetchResult and returns null
|
|
199
|
+
const result = await source.lookup(subject, signal);
|
|
200
|
+
expect(result).toBeNull();
|
|
201
|
+
});
|
|
202
|
+
it("returns null when apiKey is missing", async () => {
|
|
203
|
+
const original = process.env.GUARDIAN_API_KEY;
|
|
204
|
+
delete process.env.GUARDIAN_API_KEY;
|
|
205
|
+
const source = new GuardianSource();
|
|
206
|
+
const result = await source.lookup(subject, signal);
|
|
207
|
+
expect(result).toBeNull();
|
|
208
|
+
if (original !== undefined) {
|
|
209
|
+
process.env.GUARDIAN_API_KEY = original;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
// ==========================================================================
|
|
214
|
+
// Factory function
|
|
215
|
+
// ==========================================================================
|
|
216
|
+
describe("factory", () => {
|
|
217
|
+
it("returns a GuardianSource instance", () => {
|
|
218
|
+
const source = guardian({ apiKey: "test-key" });
|
|
219
|
+
expect(source).toBeInstanceOf(GuardianSource);
|
|
220
|
+
expect(source.name).toBe("The Guardian");
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
//# sourceMappingURL=guardian.test.js.map
|