search-web-api 1.0.13
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 +307 -0
- package/demo/index.ts +38 -0
- package/demo/openapi.ts +402 -0
- package/demo/routes/autocomplete.ts +124 -0
- package/demo/routes/search.ts +61 -0
- package/docs/AUTOCOMPLETE.md +296 -0
- package/docs/CATEGORY_SEARCH.md +265 -0
- package/package.json +32 -0
- package/src/autocomplete/autocomplete-ai-next-word-predictor.ts +47 -0
- package/src/autocomplete/autocomplete-search-engine-backends.ts +231 -0
- package/src/category-registry.ts +6 -0
- package/src/config/search-engine-constants.ts +216 -0
- package/src/constants.ts +6 -0
- package/src/engine-descriptions.ts +7 -0
- package/src/engine-status.ts +7 -0
- package/src/engine.ts +6 -0
- package/src/registry/search-engine-category-registry.ts +200 -0
- package/src/registry/search-engine-descriptions.ts +121 -0
- package/src/registry/search-engine-status-tracker.ts +240 -0
- package/src/result-container.ts +8 -0
- package/src/search/search-engines-registry-list.ts +205 -0
- package/src/search/search-query-executor.ts +168 -0
- package/src/search/search-result-container.ts +421 -0
- package/src/search-web-types.ts +7 -0
- package/src/search.ts +10 -0
- package/src/sources/academic/arxiv.ts +72 -0
- package/src/sources/academic/core.ts +65 -0
- package/src/sources/academic/crossref.ts +75 -0
- package/src/sources/academic/doaj.ts +63 -0
- package/src/sources/academic/google_scholar.ts +53 -0
- package/src/sources/academic/openalex.ts +75 -0
- package/src/sources/academic/pubmed.ts +96 -0
- package/src/sources/academic/semantic_scholar.ts +77 -0
- package/src/sources/academic/wikidata.ts +44 -0
- package/src/sources/general/baidu.ts +59 -0
- package/src/sources/general/bing.ts +30 -0
- package/src/sources/general/brave.ts +54 -0
- package/src/sources/general/duckduckgo.ts +49 -0
- package/src/sources/general/google.ts +68 -0
- package/src/sources/general/mojeek.ts +56 -0
- package/src/sources/general/qwant.ts +37 -0
- package/src/sources/general/startpage.ts +44 -0
- package/src/sources/general/yahoo.ts +41 -0
- package/src/sources/general/yandex.ts +56 -0
- package/src/sources/images/bing_images.ts +68 -0
- package/src/sources/images/deviantart.ts +71 -0
- package/src/sources/images/flickr.ts +132 -0
- package/src/sources/images/google_images.ts +101 -0
- package/src/sources/images/imgur.ts +58 -0
- package/src/sources/images/openclipart.ts +52 -0
- package/src/sources/images/pixabay.ts +56 -0
- package/src/sources/images/unsplash.ts +38 -0
- package/src/sources/images/wallhaven.ts +50 -0
- package/src/sources/it/crates.ts +41 -0
- package/src/sources/it/dockerhub.ts +47 -0
- package/src/sources/it/github.ts +37 -0
- package/src/sources/it/gitlab.ts +55 -0
- package/src/sources/it/npm.ts +36 -0
- package/src/sources/it/packagist.ts +43 -0
- package/src/sources/it/pypi.ts +30 -0
- package/src/sources/it/rubygems.ts +43 -0
- package/src/sources/it/stackoverflow.ts +39 -0
- package/src/sources/maps/apple_maps.ts +105 -0
- package/src/sources/maps/openstreetmap.ts +34 -0
- package/src/sources/maps/photon.ts +77 -0
- package/src/sources/news/bing_news.ts +95 -0
- package/src/sources/news/google_news.ts +80 -0
- package/src/sources/news/hackernews.ts +94 -0
- package/src/sources/news/yahoo_news.ts +77 -0
- package/src/sources/shopping/ebay.ts +96 -0
- package/src/sources/social/mastodon.ts +46 -0
- package/src/sources/social/medium.ts +52 -0
- package/src/sources/social/reddit.ts +48 -0
- package/src/sources/social/soundcloud.ts +64 -0
- package/src/sources/social/twitter.ts +56 -0
- package/src/sources/specialized/annas_archive.ts +97 -0
- package/src/sources/specialized/archive.ts +48 -0
- package/src/sources/specialized/genius.ts +43 -0
- package/src/sources/specialized/goodreads.ts +62 -0
- package/src/sources/specialized/imdb.ts +55 -0
- package/src/sources/specialized/openlibrary.ts +59 -0
- package/src/sources/specialized/wikipedia.ts +37 -0
- package/src/sources/specialized/wttr.ts +98 -0
- package/src/sources/torrents/1337x.ts +43 -0
- package/src/sources/torrents/eztv.ts +47 -0
- package/src/sources/torrents/kickass.ts +63 -0
- package/src/sources/torrents/nyaa.ts +53 -0
- package/src/sources/torrents/solidtorrents.ts +68 -0
- package/src/sources/torrents/thepiratebay.ts +57 -0
- package/src/sources/torrents/yts.ts +54 -0
- package/src/sources/videos/bing_videos.ts +91 -0
- package/src/sources/videos/dailymotion.ts +101 -0
- package/src/sources/videos/invidious.ts +86 -0
- package/src/sources/videos/peertube.ts +76 -0
- package/src/sources/videos/vimeo.ts +67 -0
- package/src/sources/videos/youtube.ts +78 -0
- package/src/suggest-next-words/autocomplete-ai.ts +38 -0
- package/src/suggest-next-words/autocomplete-search-engines.ts +384 -0
- package/src/suggest-next-words/misspelled-typos-8k.json +1 -0
- package/src/types/search-engine-interface.ts +27 -0
- package/src/types/search-result-types.ts +405 -0
- package/test/api.test.ts +128 -0
- package/test/autocomplete-ai.test.ts +20 -0
- package/test/autocomplete-engines.test.ts +131 -0
- package/test/engine-health-suite.test.ts +350 -0
- package/test/search.test.ts +69 -0
- package/test/sources-unit.test.ts +1152 -0
- package/test/sources.test.ts +182 -0
- package/test/test-utils.ts +81 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Engine Health Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Tests all 68+ search engines across all categories with various query types
|
|
5
|
+
* Tracks success/failure rates, response times, and errors
|
|
6
|
+
* Saves detailed health report to JSON file
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
10
|
+
import { Search } from '../src/search.js';
|
|
11
|
+
import { engineStatusTracker } from '../src/engine-status.js';
|
|
12
|
+
import { CATEGORIES } from '../src/category-registry.js';
|
|
13
|
+
import { writeFile } from 'fs/promises';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
|
|
16
|
+
// Test query configurations for different types of searches
|
|
17
|
+
const TEST_QUERIES = {
|
|
18
|
+
general: ['javascript', 'climate change', 'artificial intelligence'],
|
|
19
|
+
academic: ['machine learning', 'quantum computing', 'neural networks'],
|
|
20
|
+
it: ['typescript', 'react hooks', 'docker'],
|
|
21
|
+
images: ['sunset', 'mountains', 'cats'],
|
|
22
|
+
videos: ['tutorial', 'documentary', 'music'],
|
|
23
|
+
news: ['technology', 'science', 'politics'],
|
|
24
|
+
social: ['programming', 'technology', 'opensource'],
|
|
25
|
+
maps: ['New York', 'Paris', 'Tokyo'],
|
|
26
|
+
torrents: ['ubuntu', 'open source', 'creative commons'],
|
|
27
|
+
shopping: ['laptop', 'headphones', 'book'],
|
|
28
|
+
specialized: ['wikipedia', 'movies', 'books']
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
interface EngineTestResult {
|
|
32
|
+
engineName: string;
|
|
33
|
+
category: string;
|
|
34
|
+
status: 'success' | 'failed' | 'partial';
|
|
35
|
+
testsRun: number;
|
|
36
|
+
testsPassed: number;
|
|
37
|
+
testsFailed: number;
|
|
38
|
+
averageResponseTime: number;
|
|
39
|
+
errors: Array<{
|
|
40
|
+
query: string;
|
|
41
|
+
error: string;
|
|
42
|
+
timestamp: string;
|
|
43
|
+
}>;
|
|
44
|
+
successRate: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface CategoryTestResult {
|
|
48
|
+
category: string;
|
|
49
|
+
totalEngines: number;
|
|
50
|
+
healthyEngines: number;
|
|
51
|
+
failedEngines: number;
|
|
52
|
+
averageSuccessRate: number;
|
|
53
|
+
engines: EngineTestResult[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface HealthReport {
|
|
57
|
+
timestamp: string;
|
|
58
|
+
totalEngines: number;
|
|
59
|
+
totalTests: number;
|
|
60
|
+
overallSuccessRate: number;
|
|
61
|
+
categories: CategoryTestResult[];
|
|
62
|
+
allEngines: EngineTestResult[];
|
|
63
|
+
summary: {
|
|
64
|
+
healthy: number;
|
|
65
|
+
degraded: number;
|
|
66
|
+
failed: number;
|
|
67
|
+
averageResponseTime: number;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('Engine Health Test Suite', () => {
|
|
72
|
+
let search: Search;
|
|
73
|
+
let healthReport: HealthReport;
|
|
74
|
+
const results: EngineTestResult[] = [];
|
|
75
|
+
|
|
76
|
+
beforeAll(() => {
|
|
77
|
+
search = new Search();
|
|
78
|
+
|
|
79
|
+
// Reset all engine statuses to ensure clean testing
|
|
80
|
+
// This prevents engines from being marked as unhealthy during the test suite
|
|
81
|
+
const allEngines = search.getEngines();
|
|
82
|
+
allEngines.forEach(engineName => {
|
|
83
|
+
engineStatusTracker.resetEngine(engineName);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.log('\n🔍 Starting Comprehensive Engine Health Test Suite\n');
|
|
87
|
+
console.log(`Testing ${search.getEngines().length} engines across ${search.getCategories().length} categories\n`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('Individual Engine Tests', () => {
|
|
91
|
+
const allEngines = new Search().getEngines();
|
|
92
|
+
|
|
93
|
+
allEngines.forEach((engineName) => {
|
|
94
|
+
it(`should test engine: ${engineName}`, async () => {
|
|
95
|
+
// Reset this engine's status before testing to ensure clean state
|
|
96
|
+
engineStatusTracker.resetEngine(engineName);
|
|
97
|
+
|
|
98
|
+
const engineStatus = search.getEngineStatus(engineName);
|
|
99
|
+
const category = engineStatus?.categories[0] || 'general';
|
|
100
|
+
|
|
101
|
+
// Select appropriate test queries for this engine's category
|
|
102
|
+
const queries = TEST_QUERIES[category as keyof typeof TEST_QUERIES] || TEST_QUERIES.general;
|
|
103
|
+
|
|
104
|
+
const result: EngineTestResult = {
|
|
105
|
+
engineName,
|
|
106
|
+
category,
|
|
107
|
+
status: 'success',
|
|
108
|
+
testsRun: 0,
|
|
109
|
+
testsPassed: 0,
|
|
110
|
+
testsFailed: 0,
|
|
111
|
+
averageResponseTime: 0,
|
|
112
|
+
errors: [],
|
|
113
|
+
successRate: 0
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let totalResponseTime = 0;
|
|
117
|
+
|
|
118
|
+
// Test with multiple queries
|
|
119
|
+
for (const query of queries) {
|
|
120
|
+
result.testsRun++;
|
|
121
|
+
const startTime = Date.now();
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const searchResults = await search.search(query, 1, [engineName]);
|
|
125
|
+
const responseTime = Date.now() - startTime;
|
|
126
|
+
totalResponseTime += responseTime;
|
|
127
|
+
|
|
128
|
+
// Consider it a pass only if we get results
|
|
129
|
+
// This ensures we're actually testing the engine's ability to return data
|
|
130
|
+
if (searchResults.length > 0) {
|
|
131
|
+
result.testsPassed++;
|
|
132
|
+
} else {
|
|
133
|
+
result.testsFailed++;
|
|
134
|
+
result.errors.push({
|
|
135
|
+
query,
|
|
136
|
+
error: 'No results returned',
|
|
137
|
+
timestamp: new Date().toISOString()
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Log if it takes too long
|
|
142
|
+
if (responseTime > 10000) {
|
|
143
|
+
console.warn(`⚠️ ${engineName} slow response: ${responseTime}ms for "${query}"`);
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
result.testsFailed++;
|
|
147
|
+
result.errors.push({
|
|
148
|
+
query,
|
|
149
|
+
error: error instanceof Error ? error.message : String(error),
|
|
150
|
+
timestamp: new Date().toISOString()
|
|
151
|
+
});
|
|
152
|
+
console.error(`❌ ${engineName} failed for "${query}": ${error instanceof Error ? error.message : error}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
result.averageResponseTime = result.testsRun > 0 ? totalResponseTime / result.testsRun : 0;
|
|
157
|
+
result.successRate = result.testsRun > 0 ? (result.testsPassed / result.testsRun) * 100 : 0;
|
|
158
|
+
|
|
159
|
+
// Determine overall status
|
|
160
|
+
if (result.testsPassed === result.testsRun) {
|
|
161
|
+
result.status = 'success';
|
|
162
|
+
} else if (result.testsPassed > 0) {
|
|
163
|
+
result.status = 'partial';
|
|
164
|
+
} else {
|
|
165
|
+
result.status = 'failed';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
results.push(result);
|
|
169
|
+
|
|
170
|
+
// Assert that at least some tests passed (allow for some failures)
|
|
171
|
+
expect(result.testsPassed).toBeGreaterThanOrEqual(0);
|
|
172
|
+
|
|
173
|
+
if (result.status === 'success') {
|
|
174
|
+
console.log(`✅ ${engineName}: All tests passed (${result.testsRun}/${result.testsRun})`);
|
|
175
|
+
} else if (result.status === 'partial') {
|
|
176
|
+
console.log(`⚠️ ${engineName}: Partial success (${result.testsPassed}/${result.testsRun})`);
|
|
177
|
+
} else {
|
|
178
|
+
console.log(`❌ ${engineName}: All tests failed (0/${result.testsRun})`);
|
|
179
|
+
}
|
|
180
|
+
}, 30000); // 30 second timeout per engine
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('Category-Based Tests', () => {
|
|
185
|
+
const categories = Object.keys(CATEGORIES);
|
|
186
|
+
|
|
187
|
+
categories.forEach((category) => {
|
|
188
|
+
it(`should test all engines in category: ${category}`, async () => {
|
|
189
|
+
const engines = search.getEnginesByCategory(category);
|
|
190
|
+
const queries = TEST_QUERIES[category as keyof typeof TEST_QUERIES] || TEST_QUERIES.general;
|
|
191
|
+
|
|
192
|
+
console.log(`\n📁 Testing category: ${category} (${engines.length} engines)`);
|
|
193
|
+
|
|
194
|
+
let successfulEngines = 0;
|
|
195
|
+
let failedEngines = 0;
|
|
196
|
+
|
|
197
|
+
for (const engineName of engines) {
|
|
198
|
+
const query = queries[0]; // Use first query for category test
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const searchResults = await search.search(query, 1, [engineName]);
|
|
202
|
+
|
|
203
|
+
if (searchResults.length > 0) {
|
|
204
|
+
successfulEngines++;
|
|
205
|
+
} else {
|
|
206
|
+
failedEngines++;
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
failedEngines++;
|
|
210
|
+
console.error(` ❌ ${engineName}: ${error instanceof Error ? error.message : error}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(` ✅ Success: ${successfulEngines}/${engines.length}`);
|
|
215
|
+
console.log(` ❌ Failed: ${failedEngines}/${engines.length}`);
|
|
216
|
+
|
|
217
|
+
// Expect at least some engines in the category to work
|
|
218
|
+
expect(engines.length).toBeGreaterThan(0);
|
|
219
|
+
}, 60000); // 60 second timeout per category
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('Cross-Category Search Test', () => {
|
|
224
|
+
it('should successfully search across all categories simultaneously', async () => {
|
|
225
|
+
const query = 'technology';
|
|
226
|
+
const categories = Object.keys(CATEGORIES);
|
|
227
|
+
|
|
228
|
+
console.log(`\n🌐 Testing cross-category search with query: "${query}"`);
|
|
229
|
+
console.log(` Categories: ${categories.join(', ')}`);
|
|
230
|
+
|
|
231
|
+
const startTime = Date.now();
|
|
232
|
+
const searchResults = await search.searchByCategories(query, categories);
|
|
233
|
+
const responseTime = Date.now() - startTime;
|
|
234
|
+
|
|
235
|
+
console.log(` Results: ${searchResults.length} total results`);
|
|
236
|
+
console.log(` Time: ${responseTime}ms`);
|
|
237
|
+
|
|
238
|
+
// Group results by category
|
|
239
|
+
const resultsByCategory: { [key: string]: number } = {};
|
|
240
|
+
searchResults.forEach(result => {
|
|
241
|
+
const cat = result.category || 'unknown';
|
|
242
|
+
resultsByCategory[cat] = (resultsByCategory[cat] || 0) + 1;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
console.log(' Distribution:');
|
|
246
|
+
Object.entries(resultsByCategory).forEach(([cat, count]) => {
|
|
247
|
+
console.log(` - ${cat}: ${count} results`);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(searchResults.length).toBeGreaterThan(0);
|
|
251
|
+
}, 90000); // 90 second timeout for cross-category
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
afterAll(async () => {
|
|
255
|
+
console.log('\n📊 Generating Health Report...\n');
|
|
256
|
+
|
|
257
|
+
// Group results by category
|
|
258
|
+
const categoriesMap: { [key: string]: CategoryTestResult } = {};
|
|
259
|
+
|
|
260
|
+
results.forEach(result => {
|
|
261
|
+
if (!categoriesMap[result.category]) {
|
|
262
|
+
categoriesMap[result.category] = {
|
|
263
|
+
category: result.category,
|
|
264
|
+
totalEngines: 0,
|
|
265
|
+
healthyEngines: 0,
|
|
266
|
+
failedEngines: 0,
|
|
267
|
+
averageSuccessRate: 0,
|
|
268
|
+
engines: []
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
categoriesMap[result.category].totalEngines++;
|
|
273
|
+
categoriesMap[result.category].engines.push(result);
|
|
274
|
+
|
|
275
|
+
if (result.status === 'success') {
|
|
276
|
+
categoriesMap[result.category].healthyEngines++;
|
|
277
|
+
} else if (result.status === 'failed') {
|
|
278
|
+
categoriesMap[result.category].failedEngines++;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Calculate category averages
|
|
283
|
+
Object.values(categoriesMap).forEach(cat => {
|
|
284
|
+
const totalSuccess = cat.engines.reduce((sum, e) => sum + e.successRate, 0);
|
|
285
|
+
cat.averageSuccessRate = cat.engines.length > 0 ? totalSuccess / cat.engines.length : 0;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Calculate overall statistics
|
|
289
|
+
const totalTests = results.reduce((sum, r) => sum + r.testsRun, 0);
|
|
290
|
+
const totalPassed = results.reduce((sum, r) => sum + r.testsPassed, 0);
|
|
291
|
+
const overallSuccessRate = totalTests > 0 ? (totalPassed / totalTests) * 100 : 0;
|
|
292
|
+
|
|
293
|
+
const healthy = results.filter(r => r.status === 'success').length;
|
|
294
|
+
const degraded = results.filter(r => r.status === 'partial').length;
|
|
295
|
+
const failed = results.filter(r => r.status === 'failed').length;
|
|
296
|
+
|
|
297
|
+
const totalResponseTime = results.reduce((sum, r) => sum + r.averageResponseTime, 0);
|
|
298
|
+
const averageResponseTime = results.length > 0 ? totalResponseTime / results.length : 0;
|
|
299
|
+
|
|
300
|
+
healthReport = {
|
|
301
|
+
timestamp: new Date().toISOString(),
|
|
302
|
+
totalEngines: results.length,
|
|
303
|
+
totalTests,
|
|
304
|
+
overallSuccessRate,
|
|
305
|
+
categories: Object.values(categoriesMap),
|
|
306
|
+
allEngines: results.sort((a, b) => a.engineName.localeCompare(b.engineName)),
|
|
307
|
+
summary: {
|
|
308
|
+
healthy,
|
|
309
|
+
degraded,
|
|
310
|
+
failed,
|
|
311
|
+
averageResponseTime
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Print summary to console
|
|
316
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
317
|
+
console.log(' HEALTH REPORT SUMMARY ');
|
|
318
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
319
|
+
console.log(`Total Engines Tested: ${healthReport.totalEngines}`);
|
|
320
|
+
console.log(`Total Tests Run: ${healthReport.totalTests}`);
|
|
321
|
+
console.log(`Overall Success Rate: ${healthReport.overallSuccessRate.toFixed(2)}%`);
|
|
322
|
+
console.log(`Average Response Time: ${healthReport.summary.averageResponseTime.toFixed(0)}ms`);
|
|
323
|
+
console.log('\nEngine Status:');
|
|
324
|
+
console.log(` ✅ Healthy: ${healthReport.summary.healthy}`);
|
|
325
|
+
console.log(` ⚠️ Degraded: ${healthReport.summary.degraded}`);
|
|
326
|
+
console.log(` ❌ Failed: ${healthReport.summary.failed}`);
|
|
327
|
+
console.log('\nCategory Breakdown:');
|
|
328
|
+
healthReport.categories.forEach(cat => {
|
|
329
|
+
console.log(` ${cat.category}: ${cat.healthyEngines}/${cat.totalEngines} healthy (${cat.averageSuccessRate.toFixed(1)}% success rate)`);
|
|
330
|
+
});
|
|
331
|
+
console.log('═══════════════════════════════════════════════════════\n');
|
|
332
|
+
|
|
333
|
+
// Save to JSON file
|
|
334
|
+
const reportPath = join(process.cwd(), 'test', 'engine-health-report.json');
|
|
335
|
+
await writeFile(reportPath, JSON.stringify(healthReport, null, 2), 'utf-8');
|
|
336
|
+
console.log(`📁 Health report saved to: ${reportPath}\n`);
|
|
337
|
+
|
|
338
|
+
// Also save a simplified CSV-like report
|
|
339
|
+
const csvPath = join(process.cwd(), 'test', 'engine-health-summary.txt');
|
|
340
|
+
const csvContent = [
|
|
341
|
+
'Engine,Category,Tests Run,Tests Passed,Success Rate,Avg Response Time,Status,Errors',
|
|
342
|
+
...results.map(r =>
|
|
343
|
+
`${r.engineName},${r.category},${r.testsRun},${r.testsPassed},${r.successRate.toFixed(1)}%,${r.averageResponseTime.toFixed(0)}ms,${r.status},${r.errors.length}`
|
|
344
|
+
)
|
|
345
|
+
].join('\n');
|
|
346
|
+
|
|
347
|
+
await writeFile(csvPath, csvContent, 'utf-8');
|
|
348
|
+
console.log(`📁 Summary report saved to: ${csvPath}\n`);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Search } from "../src/search";
|
|
3
|
+
import { validateEngineResults } from "./test-utils";
|
|
4
|
+
|
|
5
|
+
describe("Search Class", () => {
|
|
6
|
+
const search = new Search();
|
|
7
|
+
|
|
8
|
+
it("should search across all engines by default", async () => {
|
|
9
|
+
const results = await search.search("javascript", 1);
|
|
10
|
+
|
|
11
|
+
expect(results).toBeDefined();
|
|
12
|
+
expect(Array.isArray(results)).toBe(true);
|
|
13
|
+
expect(validateEngineResults(results)).toBe(true);
|
|
14
|
+
}, 60000);
|
|
15
|
+
|
|
16
|
+
it("should filter by engine name", async () => {
|
|
17
|
+
const results = await search.search("react", 1, ["github"]);
|
|
18
|
+
|
|
19
|
+
expect(results).toBeDefined();
|
|
20
|
+
expect(Array.isArray(results)).toBe(true);
|
|
21
|
+
|
|
22
|
+
if (results.length > 0) {
|
|
23
|
+
results.forEach((result) => {
|
|
24
|
+
expect(result.engine).toBe("github");
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}, 30000);
|
|
28
|
+
|
|
29
|
+
it("should filter by category", async () => {
|
|
30
|
+
const results = await search.search("ubuntu", 1, undefined, ["torrent"]);
|
|
31
|
+
|
|
32
|
+
expect(results).toBeDefined();
|
|
33
|
+
expect(Array.isArray(results)).toBe(true);
|
|
34
|
+
|
|
35
|
+
if (results.length > 0) {
|
|
36
|
+
const torrentEngines = ["1337x", "thepiratebay", "nyaa", "yts", "eztv"];
|
|
37
|
+
results.forEach((result) => {
|
|
38
|
+
expect(torrentEngines).toContain(result.engine);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}, 60000);
|
|
42
|
+
|
|
43
|
+
it("should handle pagination", async () => {
|
|
44
|
+
const page1 = await search.search("python", 1, ["google"]);
|
|
45
|
+
const page2 = await search.search("python", 2, ["google"]);
|
|
46
|
+
|
|
47
|
+
expect(page1).toBeDefined();
|
|
48
|
+
expect(page2).toBeDefined();
|
|
49
|
+
expect(Array.isArray(page1)).toBe(true);
|
|
50
|
+
expect(Array.isArray(page2)).toBe(true);
|
|
51
|
+
}, 60000);
|
|
52
|
+
|
|
53
|
+
it("should handle multiple engine filters", async () => {
|
|
54
|
+
const results = await search.search("nodejs", 1, [
|
|
55
|
+
"github",
|
|
56
|
+
"npm",
|
|
57
|
+
"stackoverflow",
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
expect(results).toBeDefined();
|
|
61
|
+
expect(Array.isArray(results)).toBe(true);
|
|
62
|
+
|
|
63
|
+
if (results.length > 0) {
|
|
64
|
+
results.forEach((result) => {
|
|
65
|
+
expect(["github", "npm", "stackoverflow"]).toContain(result.engine);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}, 60000);
|
|
69
|
+
});
|