@oevortex/ddg_search 1.2.0 → 1.2.2
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/CHANGELOG.md +47 -26
- package/README.md +3 -3
- package/babel.config.js +11 -11
- package/bin/cli.js +6 -6
- package/package.json +2 -2
- package/src/tools/searchTool.js +40 -40
- package/src/utils/search.js +322 -322
- package/src/utils/search_iask.js +228 -228
- package/src/utils/search_monica.js +238 -238
- package/test.setup.js +72 -119
|
@@ -2,241 +2,241 @@ import axios from 'axios';
|
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
3
|
import { getRandomUserAgent } from './user_agents.js';
|
|
4
4
|
|
|
5
|
-
class MonicaClient {
|
|
6
|
-
constructor(timeout = 60000) {
|
|
7
|
-
this.apiEndpoint = "https://monica.so/api/search_v1/search";
|
|
8
|
-
this.timeout = timeout;
|
|
9
|
-
this.clientId = randomUUID();
|
|
10
|
-
this.sessionId = "";
|
|
11
|
-
|
|
12
|
-
this.headers = {
|
|
13
|
-
"accept": "*/*",
|
|
14
|
-
"accept-encoding": "gzip, deflate, br, zstd",
|
|
15
|
-
"accept-language": "en-US,en;q=0.9",
|
|
16
|
-
"content-type": "application/json",
|
|
17
|
-
"dnt": "1",
|
|
18
|
-
"origin": "https://monica.so",
|
|
19
|
-
"referer": "https://monica.so/answers",
|
|
20
|
-
"sec-ch-ua": '"Microsoft Edge";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
|
|
21
|
-
"sec-ch-ua-mobile": "?0",
|
|
22
|
-
"sec-ch-ua-platform": '"Windows"',
|
|
23
|
-
"sec-fetch-dest": "empty",
|
|
24
|
-
"sec-fetch-mode": "cors",
|
|
25
|
-
"sec-fetch-site": "same-origin",
|
|
26
|
-
"sec-gpc": "1",
|
|
27
|
-
"user-agent": getRandomUserAgent(),
|
|
28
|
-
"x-client-id": this.clientId,
|
|
29
|
-
"x-client-locale": "en",
|
|
30
|
-
"x-client-type": "web",
|
|
31
|
-
"x-client-version": "5.4.3",
|
|
32
|
-
"x-from-channel": "NA",
|
|
33
|
-
"x-product-name": "Monica-Search",
|
|
34
|
-
"x-time-zone": "Asia/Calcutta;-330"
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Axios instance with improved configuration
|
|
38
|
-
this.client = axios.create({
|
|
39
|
-
headers: this.headers,
|
|
40
|
-
timeout: this.timeout,
|
|
41
|
-
withCredentials: true,
|
|
42
|
-
validateStatus: (status) => status >= 200 && status < 500 // Accept non-error status codes
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
formatResponse(text) {
|
|
47
|
-
try {
|
|
48
|
-
// Clean up markdown formatting
|
|
49
|
-
let cleanedText = text.replace(/\*\*/g, '');
|
|
50
|
-
|
|
51
|
-
// Remove any empty lines
|
|
52
|
-
cleanedText = cleanedText.replace(/\n\s*\n/g, '\n\n');
|
|
53
|
-
|
|
54
|
-
// Remove any trailing whitespace
|
|
55
|
-
return cleanedText.trim();
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.error('Error formatting Monica response:', error.message);
|
|
58
|
-
return text.trim(); // Return original if formatting fails
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async search(prompt) {
|
|
63
|
-
// Input validation
|
|
64
|
-
if (!prompt || typeof prompt !== 'string') {
|
|
65
|
-
throw new Error('Invalid prompt: must be a non-empty string');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (prompt.length > 5000) {
|
|
69
|
-
throw new Error('Invalid prompt: too long (maximum 5000 characters)');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const taskId = randomUUID();
|
|
73
|
-
const payload = {
|
|
74
|
-
"pro": false,
|
|
75
|
-
"query": prompt,
|
|
76
|
-
"round": 1,
|
|
77
|
-
"session_id": this.sessionId,
|
|
78
|
-
"language": "auto",
|
|
79
|
-
"task_id": taskId
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const cookies = {
|
|
83
|
-
"monica_home_theme": "auto"
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// Convert cookies object to string
|
|
87
|
-
const cookieString = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
console.log(`Monica API request starting: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`);
|
|
91
|
-
|
|
92
|
-
const response = await this.client.post(this.apiEndpoint, payload, {
|
|
93
|
-
headers: {
|
|
94
|
-
...this.headers,
|
|
95
|
-
'Cookie': cookieString
|
|
96
|
-
},
|
|
97
|
-
responseType: 'stream',
|
|
98
|
-
validateStatus: function (status) {
|
|
99
|
-
return status < 500; // Accept non-error responses
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
let fullText = '';
|
|
104
|
-
let receivedData = false;
|
|
105
|
-
|
|
106
|
-
return new Promise((resolve, reject) => {
|
|
107
|
-
const timeoutId = setTimeout(() => {
|
|
108
|
-
reject(new Error('Monica stream timeout: no response data received'));
|
|
109
|
-
}, this.timeout);
|
|
110
|
-
|
|
111
|
-
response.data.on('data', (chunk) => {
|
|
112
|
-
receivedData = true;
|
|
113
|
-
const lines = chunk.toString().split('\n');
|
|
114
|
-
|
|
115
|
-
for (const line of lines) {
|
|
116
|
-
if (line.startsWith('data: ')) {
|
|
117
|
-
try {
|
|
118
|
-
const jsonStr = line.substring(6);
|
|
119
|
-
const data = JSON.parse(jsonStr);
|
|
120
|
-
|
|
121
|
-
if (data.session_id) {
|
|
122
|
-
this.sessionId = data.session_id;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (data.text) {
|
|
126
|
-
fullText += data.text;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
console.log('Monica data chunk received:', data.text?.substring(0, 50) + '...');
|
|
130
|
-
} catch (e) {
|
|
131
|
-
// Ignore parse errors for non-JSON lines
|
|
132
|
-
console.debug('Ignoring non-JSON line:', line.substring(0, 50));
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
response.data.on('end', () => {
|
|
139
|
-
clearTimeout(timeoutId);
|
|
140
|
-
|
|
141
|
-
if (!receivedData) {
|
|
142
|
-
reject(new Error('Monica no data received: empty response'));
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
console.log('Monica stream completed, total length:', fullText.length);
|
|
147
|
-
|
|
148
|
-
const formatted = this.formatResponse(fullText);
|
|
149
|
-
|
|
150
|
-
if (!formatted || formatted.trim() === '') {
|
|
151
|
-
reject(new Error('Monica no valid content: received empty or invalid response'));
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
resolve(formatted);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
response.data.on('error', (err) => {
|
|
159
|
-
clearTimeout(timeoutId);
|
|
160
|
-
console.error('Monica stream error:', err.message);
|
|
161
|
-
|
|
162
|
-
if (err.code === 'ENOTFOUND') {
|
|
163
|
-
reject(new Error('Monica network error: unable to resolve host'));
|
|
164
|
-
} else if (err.code === 'ECONNREFUSED') {
|
|
165
|
-
reject(new Error('Monica network error: connection refused'));
|
|
166
|
-
} else {
|
|
167
|
-
reject(new Error(`Monica stream error: ${err.message}`));
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.error('Monica API request failed:', error.message);
|
|
174
|
-
|
|
175
|
-
if (error.response) {
|
|
176
|
-
// HTTP error response
|
|
177
|
-
const status = error.response.status;
|
|
178
|
-
if (status === 429) {
|
|
179
|
-
throw new Error('Monica rate limit: too many requests');
|
|
180
|
-
} else if (status >= 500) {
|
|
181
|
-
throw new Error(`Monica server error: HTTP ${status}`);
|
|
182
|
-
} else if (status >= 400) {
|
|
183
|
-
throw new Error(`Monica client error: HTTP ${status}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (error.code === 'ECONNABORTED') {
|
|
188
|
-
throw new Error('Monica request timeout: took too long');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
throw new Error(`Monica API request failed: ${error.message}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Search using Monica AI
|
|
198
|
-
* @param {string} query - The search query
|
|
199
|
-
* @returns {Promise<string>} The search results
|
|
200
|
-
*/
|
|
201
|
-
export async function searchMonica(query) {
|
|
202
|
-
// Input validation
|
|
203
|
-
if (!query || typeof query !== 'string') {
|
|
204
|
-
throw new Error('Invalid query: query must be a non-empty string');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
console.log(`Monica AI search starting: "${query}"`);
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
const client = new MonicaClient();
|
|
211
|
-
const result = await client.search(query);
|
|
212
|
-
|
|
213
|
-
if (result && result.trim()) {
|
|
214
|
-
console.log(`Monica AI search completed: ${result.length} characters received`);
|
|
215
|
-
} else {
|
|
216
|
-
console.log('Monica AI search completed but returned empty result');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return result;
|
|
220
|
-
} catch (error) {
|
|
221
|
-
console.error('Error in Monica AI search:', error.message);
|
|
222
|
-
|
|
223
|
-
// Enhanced error handling
|
|
224
|
-
if (error.code === 'ENOTFOUND') {
|
|
225
|
-
throw new Error('Monica network error: unable to resolve host');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (error.code === 'ECONNREFUSED') {
|
|
229
|
-
throw new Error('Monica network error: connection refused');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (error.message.includes('timeout')) {
|
|
233
|
-
throw new Error('Monica timeout: request took too long');
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (error.message.includes('network')) {
|
|
237
|
-
throw new Error('Monica network error: service may be unavailable');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
throw new Error(`Monica search failed for "${query}": ${error.message}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
5
|
+
class MonicaClient {
|
|
6
|
+
constructor(timeout = 60000) {
|
|
7
|
+
this.apiEndpoint = "https://monica.so/api/search_v1/search";
|
|
8
|
+
this.timeout = timeout;
|
|
9
|
+
this.clientId = randomUUID();
|
|
10
|
+
this.sessionId = "";
|
|
11
|
+
|
|
12
|
+
this.headers = {
|
|
13
|
+
"accept": "*/*",
|
|
14
|
+
"accept-encoding": "gzip, deflate, br, zstd",
|
|
15
|
+
"accept-language": "en-US,en;q=0.9",
|
|
16
|
+
"content-type": "application/json",
|
|
17
|
+
"dnt": "1",
|
|
18
|
+
"origin": "https://monica.so",
|
|
19
|
+
"referer": "https://monica.so/answers",
|
|
20
|
+
"sec-ch-ua": '"Microsoft Edge";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
|
|
21
|
+
"sec-ch-ua-mobile": "?0",
|
|
22
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
23
|
+
"sec-fetch-dest": "empty",
|
|
24
|
+
"sec-fetch-mode": "cors",
|
|
25
|
+
"sec-fetch-site": "same-origin",
|
|
26
|
+
"sec-gpc": "1",
|
|
27
|
+
"user-agent": getRandomUserAgent(),
|
|
28
|
+
"x-client-id": this.clientId,
|
|
29
|
+
"x-client-locale": "en",
|
|
30
|
+
"x-client-type": "web",
|
|
31
|
+
"x-client-version": "5.4.3",
|
|
32
|
+
"x-from-channel": "NA",
|
|
33
|
+
"x-product-name": "Monica-Search",
|
|
34
|
+
"x-time-zone": "Asia/Calcutta;-330"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Axios instance with improved configuration
|
|
38
|
+
this.client = axios.create({
|
|
39
|
+
headers: this.headers,
|
|
40
|
+
timeout: this.timeout,
|
|
41
|
+
withCredentials: true,
|
|
42
|
+
validateStatus: (status) => status >= 200 && status < 500 // Accept non-error status codes
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
formatResponse(text) {
|
|
47
|
+
try {
|
|
48
|
+
// Clean up markdown formatting
|
|
49
|
+
let cleanedText = text.replace(/\*\*/g, '');
|
|
50
|
+
|
|
51
|
+
// Remove any empty lines
|
|
52
|
+
cleanedText = cleanedText.replace(/\n\s*\n/g, '\n\n');
|
|
53
|
+
|
|
54
|
+
// Remove any trailing whitespace
|
|
55
|
+
return cleanedText.trim();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error formatting Monica response:', error.message);
|
|
58
|
+
return text.trim(); // Return original if formatting fails
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async search(prompt) {
|
|
63
|
+
// Input validation
|
|
64
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
65
|
+
throw new Error('Invalid prompt: must be a non-empty string');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (prompt.length > 5000) {
|
|
69
|
+
throw new Error('Invalid prompt: too long (maximum 5000 characters)');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const taskId = randomUUID();
|
|
73
|
+
const payload = {
|
|
74
|
+
"pro": false,
|
|
75
|
+
"query": prompt,
|
|
76
|
+
"round": 1,
|
|
77
|
+
"session_id": this.sessionId,
|
|
78
|
+
"language": "auto",
|
|
79
|
+
"task_id": taskId
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const cookies = {
|
|
83
|
+
"monica_home_theme": "auto"
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Convert cookies object to string
|
|
87
|
+
const cookieString = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
console.log(`Monica API request starting: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`);
|
|
91
|
+
|
|
92
|
+
const response = await this.client.post(this.apiEndpoint, payload, {
|
|
93
|
+
headers: {
|
|
94
|
+
...this.headers,
|
|
95
|
+
'Cookie': cookieString
|
|
96
|
+
},
|
|
97
|
+
responseType: 'stream',
|
|
98
|
+
validateStatus: function (status) {
|
|
99
|
+
return status < 500; // Accept non-error responses
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
let fullText = '';
|
|
104
|
+
let receivedData = false;
|
|
105
|
+
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const timeoutId = setTimeout(() => {
|
|
108
|
+
reject(new Error('Monica stream timeout: no response data received'));
|
|
109
|
+
}, this.timeout);
|
|
110
|
+
|
|
111
|
+
response.data.on('data', (chunk) => {
|
|
112
|
+
receivedData = true;
|
|
113
|
+
const lines = chunk.toString().split('\n');
|
|
114
|
+
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
if (line.startsWith('data: ')) {
|
|
117
|
+
try {
|
|
118
|
+
const jsonStr = line.substring(6);
|
|
119
|
+
const data = JSON.parse(jsonStr);
|
|
120
|
+
|
|
121
|
+
if (data.session_id) {
|
|
122
|
+
this.sessionId = data.session_id;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (data.text) {
|
|
126
|
+
fullText += data.text;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('Monica data chunk received:', data.text?.substring(0, 50) + '...');
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// Ignore parse errors for non-JSON lines
|
|
132
|
+
console.debug('Ignoring non-JSON line:', line.substring(0, 50));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
response.data.on('end', () => {
|
|
139
|
+
clearTimeout(timeoutId);
|
|
140
|
+
|
|
141
|
+
if (!receivedData) {
|
|
142
|
+
reject(new Error('Monica no data received: empty response'));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log('Monica stream completed, total length:', fullText.length);
|
|
147
|
+
|
|
148
|
+
const formatted = this.formatResponse(fullText);
|
|
149
|
+
|
|
150
|
+
if (!formatted || formatted.trim() === '') {
|
|
151
|
+
reject(new Error('Monica no valid content: received empty or invalid response'));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
resolve(formatted);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
response.data.on('error', (err) => {
|
|
159
|
+
clearTimeout(timeoutId);
|
|
160
|
+
console.error('Monica stream error:', err.message);
|
|
161
|
+
|
|
162
|
+
if (err.code === 'ENOTFOUND') {
|
|
163
|
+
reject(new Error('Monica network error: unable to resolve host'));
|
|
164
|
+
} else if (err.code === 'ECONNREFUSED') {
|
|
165
|
+
reject(new Error('Monica network error: connection refused'));
|
|
166
|
+
} else {
|
|
167
|
+
reject(new Error(`Monica stream error: ${err.message}`));
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error('Monica API request failed:', error.message);
|
|
174
|
+
|
|
175
|
+
if (error.response) {
|
|
176
|
+
// HTTP error response
|
|
177
|
+
const status = error.response.status;
|
|
178
|
+
if (status === 429) {
|
|
179
|
+
throw new Error('Monica rate limit: too many requests');
|
|
180
|
+
} else if (status >= 500) {
|
|
181
|
+
throw new Error(`Monica server error: HTTP ${status}`);
|
|
182
|
+
} else if (status >= 400) {
|
|
183
|
+
throw new Error(`Monica client error: HTTP ${status}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (error.code === 'ECONNABORTED') {
|
|
188
|
+
throw new Error('Monica request timeout: took too long');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
throw new Error(`Monica API request failed: ${error.message}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Search using Monica AI
|
|
198
|
+
* @param {string} query - The search query
|
|
199
|
+
* @returns {Promise<string>} The search results
|
|
200
|
+
*/
|
|
201
|
+
export async function searchMonica(query) {
|
|
202
|
+
// Input validation
|
|
203
|
+
if (!query || typeof query !== 'string') {
|
|
204
|
+
throw new Error('Invalid query: query must be a non-empty string');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(`Monica AI search starting: "${query}"`);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const client = new MonicaClient();
|
|
211
|
+
const result = await client.search(query);
|
|
212
|
+
|
|
213
|
+
if (result && result.trim()) {
|
|
214
|
+
console.log(`Monica AI search completed: ${result.length} characters received`);
|
|
215
|
+
} else {
|
|
216
|
+
console.log('Monica AI search completed but returned empty result');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return result;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error('Error in Monica AI search:', error.message);
|
|
222
|
+
|
|
223
|
+
// Enhanced error handling
|
|
224
|
+
if (error.code === 'ENOTFOUND') {
|
|
225
|
+
throw new Error('Monica network error: unable to resolve host');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (error.code === 'ECONNREFUSED') {
|
|
229
|
+
throw new Error('Monica network error: connection refused');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (error.message.includes('timeout')) {
|
|
233
|
+
throw new Error('Monica timeout: request took too long');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (error.message.includes('network')) {
|
|
237
|
+
throw new Error('Monica network error: service may be unavailable');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error(`Monica search failed for "${query}": ${error.message}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
package/test.setup.js
CHANGED
|
@@ -1,120 +1,73 @@
|
|
|
1
|
-
// Global test setup
|
|
2
|
-
import { jest } from '@jest/globals';
|
|
3
|
-
|
|
4
|
-
// Mock console methods to reduce noise in tests
|
|
5
|
-
global.console = {
|
|
6
|
-
...console,
|
|
7
|
-
log: jest.fn(),
|
|
8
|
-
error: jest.fn(),
|
|
9
|
-
warn: jest.fn(),
|
|
10
|
-
info: jest.fn(),
|
|
11
|
-
debug: jest.fn()
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// Increase test timeout for integration tests
|
|
15
|
-
jest.setTimeout(30000);
|
|
16
|
-
|
|
17
|
-
// Global test utilities
|
|
18
|
-
global.mockDelay = (ms = 100) => new Promise(resolve => setTimeout(resolve, ms));
|
|
19
|
-
|
|
20
|
-
// Mock WebSocket class
|
|
21
|
-
global.WebSocket = jest.fn().mockImplementation(() => ({
|
|
22
|
-
close: jest.fn(),
|
|
23
|
-
send: jest.fn(),
|
|
24
|
-
addEventListener: jest.fn(),
|
|
25
|
-
removeEventListener: jest.fn()
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
// Mock axios
|
|
29
|
-
const mockAxios = jest.fn(() => Promise.resolve({
|
|
30
|
-
status: 200,
|
|
31
|
-
data: '<html><body>Mock Response</body></html>',
|
|
32
|
-
config: { url: 'http://example.com' },
|
|
33
|
-
request: { res: { responseUrl: 'http://example.com' } }
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
mockAxios.get = jest.fn();
|
|
37
|
-
mockAxios.post = jest.fn();
|
|
38
|
-
|
|
39
|
-
jest.mock('axios', () => mockAxios);
|
|
40
|
-
|
|
41
|
-
// Mock external modules
|
|
42
|
-
jest.mock('cheerio', () => ({
|
|
43
|
-
load: jest.fn(() => ({
|
|
44
|
-
find: jest.fn(() => ({
|
|
45
|
-
each: jest.fn(),
|
|
46
|
-
text: jest.fn(() => 'Mock Title'),
|
|
47
|
-
attr: jest.fn(() => 'http://example.com')
|
|
48
|
-
})),
|
|
49
|
-
html: jest.fn(() => '<div>Mock Content</div>'),
|
|
50
|
-
text: jest.fn(() => 'Mock Text Content')
|
|
51
|
-
}))
|
|
52
|
-
}));
|
|
53
|
-
|
|
54
|
-
jest.mock('ws', () => jest.fn());
|
|
55
|
-
|
|
56
|
-
jest.mock('turndown', () => jest.fn(() => ({
|
|
57
|
-
turndown: jest.fn((html) => html.replace(/<[^>]*>/g, ''))
|
|
58
|
-
})));
|
|
59
|
-
|
|
60
|
-
jest.mock('tough-cookie', () => ({
|
|
61
|
-
CookieJar: jest.fn(() => ({
|
|
62
|
-
getCookies: jest.fn(() => []),
|
|
63
|
-
setCookie: jest.fn()
|
|
64
|
-
}))
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
|
-
jest.mock('axios-cookiejar-support', () => ({
|
|
68
|
-
wrapper: jest.fn((axios) => axios)
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
jest.mock('crypto', () => ({
|
|
72
|
-
randomUUID: jest.fn(() => 'mock-uuid-12345')
|
|
73
|
-
}));
|
|
74
|
-
|
|
75
|
-
// Mock axios
|
|
76
|
-
const mockAxios = jest.fn(() => Promise.resolve({
|
|
77
|
-
status: 200,
|
|
78
|
-
data: '<html><body>Mock Response</body></html>',
|
|
79
|
-
config: { url: 'http://example.com' },
|
|
80
|
-
request: { res: { responseUrl: 'http://example.com' } }
|
|
81
|
-
}));
|
|
82
|
-
|
|
83
|
-
mockAxios.get = jest.fn();
|
|
84
|
-
mockAxios.post = jest.fn();
|
|
85
|
-
|
|
86
|
-
jest.mock('axios', () => mockAxios);
|
|
87
|
-
|
|
88
|
-
// Mock external modules
|
|
89
|
-
jest.mock('cheerio', () => ({
|
|
90
|
-
load: jest.fn(() => ({
|
|
91
|
-
find: jest.fn(() => ({
|
|
92
|
-
each: jest.fn(),
|
|
93
|
-
text: jest.fn(() => 'Mock Title'),
|
|
94
|
-
attr: jest.fn(() => 'http://example.com')
|
|
95
|
-
})),
|
|
96
|
-
html: jest.fn(() => '<div>Mock Content</div>'),
|
|
97
|
-
text: jest.fn(() => 'Mock Text Content')
|
|
98
|
-
}))
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
jest.mock('ws', () => jest.fn());
|
|
102
|
-
|
|
103
|
-
jest.mock('turndown', () => jest.fn(() => ({
|
|
104
|
-
turndown: jest.fn((html) => html.replace(/<[^>]*>/g, ''))
|
|
105
|
-
})));
|
|
106
|
-
|
|
107
|
-
jest.mock('tough-cookie', () => ({
|
|
108
|
-
CookieJar: jest.fn(() => ({
|
|
109
|
-
getCookies: jest.fn(() => []),
|
|
110
|
-
setCookie: jest.fn()
|
|
111
|
-
}))
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
jest.mock('axios-cookiejar-support', () => ({
|
|
115
|
-
wrapper: jest.fn((axios) => axios)
|
|
116
|
-
}));
|
|
117
|
-
|
|
118
|
-
jest.mock('crypto', () => ({
|
|
119
|
-
randomUUID: jest.fn(() => 'mock-uuid-12345')
|
|
1
|
+
// Global test setup
|
|
2
|
+
import { jest } from '@jest/globals';
|
|
3
|
+
|
|
4
|
+
// Mock console methods to reduce noise in tests
|
|
5
|
+
global.console = {
|
|
6
|
+
...console,
|
|
7
|
+
log: jest.fn(),
|
|
8
|
+
error: jest.fn(),
|
|
9
|
+
warn: jest.fn(),
|
|
10
|
+
info: jest.fn(),
|
|
11
|
+
debug: jest.fn()
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Increase test timeout for integration tests
|
|
15
|
+
jest.setTimeout(30000);
|
|
16
|
+
|
|
17
|
+
// Global test utilities
|
|
18
|
+
global.mockDelay = (ms = 100) => new Promise(resolve => setTimeout(resolve, ms));
|
|
19
|
+
|
|
20
|
+
// Mock WebSocket class
|
|
21
|
+
global.WebSocket = jest.fn().mockImplementation(() => ({
|
|
22
|
+
close: jest.fn(),
|
|
23
|
+
send: jest.fn(),
|
|
24
|
+
addEventListener: jest.fn(),
|
|
25
|
+
removeEventListener: jest.fn()
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock axios
|
|
29
|
+
const mockAxios = jest.fn(() => Promise.resolve({
|
|
30
|
+
status: 200,
|
|
31
|
+
data: '<html><body>Mock Response</body></html>',
|
|
32
|
+
config: { url: 'http://example.com' },
|
|
33
|
+
request: { res: { responseUrl: 'http://example.com' } }
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
mockAxios.get = jest.fn();
|
|
37
|
+
mockAxios.post = jest.fn();
|
|
38
|
+
|
|
39
|
+
jest.mock('axios', () => mockAxios);
|
|
40
|
+
|
|
41
|
+
// Mock external modules
|
|
42
|
+
jest.mock('cheerio', () => ({
|
|
43
|
+
load: jest.fn(() => ({
|
|
44
|
+
find: jest.fn(() => ({
|
|
45
|
+
each: jest.fn(),
|
|
46
|
+
text: jest.fn(() => 'Mock Title'),
|
|
47
|
+
attr: jest.fn(() => 'http://example.com')
|
|
48
|
+
})),
|
|
49
|
+
html: jest.fn(() => '<div>Mock Content</div>'),
|
|
50
|
+
text: jest.fn(() => 'Mock Text Content')
|
|
51
|
+
}))
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
jest.mock('ws', () => jest.fn());
|
|
55
|
+
|
|
56
|
+
jest.mock('turndown', () => jest.fn(() => ({
|
|
57
|
+
turndown: jest.fn((html) => html.replace(/<[^>]*>/g, ''))
|
|
58
|
+
})));
|
|
59
|
+
|
|
60
|
+
jest.mock('tough-cookie', () => ({
|
|
61
|
+
CookieJar: jest.fn(() => ({
|
|
62
|
+
getCookies: jest.fn(() => []),
|
|
63
|
+
setCookie: jest.fn()
|
|
64
|
+
}))
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
jest.mock('axios-cookiejar-support', () => ({
|
|
68
|
+
wrapper: jest.fn((axios) => axios)
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
jest.mock('crypto', () => ({
|
|
72
|
+
randomUUID: jest.fn(() => 'mock-uuid-12345')
|
|
120
73
|
}));
|