Repository navigation
Expand file tree
/
Copy pathtest.js
More file actions
307 lines (281 loc) · 18.2 KB
/
Copy pathtest.js
File metadata and controls
307 lines (281 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
// Test rapide contre l'API réelle en prod — pas de mock, valide le vrai comportement.
const presend = require('./index.js');
async function main() {
let passed = 0, failed = 0;
async function check(name, fn) {
try {
await fn();
console.log('✅ ' + name);
passed++;
} catch (e) {
console.log('❌ ' + name + ' — ' + e.message);
failed++;
}
}
// Pour les tests qui dépendent d'un service tiers avec des pannes
// documentées comme occasionnelles (ex: crt.sh) -- 3 tentatives
// avant d'échouer réellement, pour ne pas bloquer une publication
// pour une instabilité qui n'a rien à voir avec notre code.
// non_blocking: après épuisement des tentatives, log un avertissement
// au lieu d'échouer la suite -- pour les tests dont la seule cause de
// panne possible est un service tiers qu'on ne contrôle pas (ex: crt.sh),
// pas un vrai bug dans notre code publié.
async function checkWithRetry(name, fn, attempts = 3, nonBlocking = false) {
for (let i = 1; i <= attempts; i++) {
try {
await fn();
console.log('✅ ' + name + (i > 1 ? ' (réussi à la tentative ' + i + ')' : ''));
passed++;
return;
} catch (e) {
if (i === attempts) {
if (nonBlocking) {
console.log('⚠️ ' + name + ' — ' + e.message + ' (après ' + attempts + ' tentatives, non bloquant : dépendance tierce)');
} else {
console.log('❌ ' + name + ' — ' + e.message + ' (après ' + attempts + ' tentatives)');
failed++;
}
} else {
await new Promise((r) => setTimeout(r, 2000 * i));
}
}
}
}
await check('generateUuid returns a valid UUID', async () => {
const r = await presend.generateUuid(1);
if (!/^[0-9a-f-]{36}$/.test(r.uuids[0])) throw new Error('Invalid UUID format');
});
await check('cleanUrl strips utm params', async () => {
const r = await presend.cleanUrl('https://example.com?utm_source=test');
if (r.clean !== 'https://example.com/') throw new Error('Got: ' + r.clean);
});
await check('base64Encode/Decode roundtrip', async () => {
const enc = await presend.base64Encode('hello presend');
const dec = await presend.base64Decode(enc.result);
if (dec.result !== 'hello presend') throw new Error('Roundtrip mismatch');
});
await check('convertColor hex to rgb', async () => {
const r = await presend.convertColor({ hex: 'ff5733' });
if (r.rgb_values.r !== 255) throw new Error('Wrong r value');
});
await check('timestamp converts unix correctly', async () => {
const r = await presend.timestamp({ unix: 1700000000 });
if (r.iso_8601 !== '2023-11-14T22:13:20.000Z') throw new Error('Got: ' + r.iso_8601);
});
await check('parseUserAgent detects Chrome', async () => {
const r = await presend.parseUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36');
if (r.parsed.browser !== 'Chrome') throw new Error('Got: ' + r.parsed.browser);
});
await check('decodeJwt decodes header and payload', async () => {
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.dummy';
const r = await presend.decodeJwt(token);
if (r.payload.name !== 'John Doe') throw new Error('Got: ' + JSON.stringify(r.payload));
});
await check('getIp returns an ip field', async () => {
const r = await presend.getIp();
if (!r.ip) throw new Error('No ip field in response');
});
await check('generatePassword respects length', async () => {
const r = await presend.generatePassword({ length: 24, symbols: true });
if (r.password.length !== 24) throw new Error('Got length: ' + r.password.length);
});
await check('validateEmail confirms gmail has MX', async () => {
const r = await presend.validateEmail('test@gmail.com');
if (!r.has_mx) throw new Error('Expected gmail.com to have MX records');
});
await check('isDisposableEmail detects mailinator', async () => {
const r = await presend.isDisposableEmail('test@mailinator.com');
if (!r.disposable) throw new Error('Expected mailinator.com to be flagged disposable');
});
await check('checkPasswordBreach detects common password', async () => {
const r = await presend.checkPasswordBreach('password123');
if (!r.breached) throw new Error('Expected password123 to be breached');
});
await check('getFavicon returns a url', async () => {
const r = await presend.getFavicon('github.com');
if (!r.favicon || !r.favicon.startsWith('http')) throw new Error('Got: ' + JSON.stringify(r));
});
await check('jsonToCsv converts correctly', async () => {
const r = await presend.jsonToCsv('[{"a":1,"b":2}]');
if (r.result !== 'a,b\na value expected...'.split('...')[0] && !r.result.includes('a,b')) {
// vérif simple : la ligne d'en-tête doit être présente
if (!r.result.startsWith('a,b')) throw new Error('Got: ' + r.result);
}
});
await check('csvToJson converts correctly', async () => {
const r = await presend.csvToJson('a,b\n1,2');
const parsed = JSON.parse(r.result);
if (parsed[0].a !== '1') throw new Error('Got: ' + r.result);
});
await check('securityHeaders grades a known-good site', async () => {
const r = await presend.securityHeaders('https://presend.pages.dev');
if (typeof r.score !== 'number' || !r.grade) throw new Error('Got: ' + JSON.stringify(r));
});
await check('urlReputation returns a malicious boolean', async () => {
const r = await presend.urlReputation('https://github.com');
if (typeof r.malicious !== 'boolean') throw new Error('Got: ' + JSON.stringify(r));
});
await checkWithRetry('findSubdomains returns a subdomains array', async () => {
const r = await presend.findSubdomains('presend.pages.dev');
if (!Array.isArray(r.subdomains)) throw new Error('Got: ' + JSON.stringify(r));
}, 3, true);
// --- 5 new chained endpoints ---
await check('emailVerify detects a disposable domain', async () => {
const r = await presend.emailVerify('test@mailinator.com');
if (r.disposable !== true) throw new Error('Got: ' + JSON.stringify(r));
});
await check('passwordCheck flags a known-breached weak password', async () => {
const r = await presend.passwordCheck('password123', true);
if (r.strength !== 'very_weak' || r.strength_reason !== 'found_in_breaches' || !r.breach || r.breach.breached !== true) {
throw new Error('Got: ' + JSON.stringify(r));
}
});
await check('phoneVerify validates an E.164 number', async () => {
const r = await presend.phoneVerify('+14155552671');
if (r.valid !== true || r.country !== 'US') throw new Error('Got: ' + JSON.stringify(r));
});
await check('cleanImage strips EXIF and returns a smaller buffer', async () => {
const fs = require('fs');
const original = fs.readFileSync('/tmp/test-fixtures/photo.jpg');
const r = await presend.cleanImage(original, 'image/jpeg');
if (!Buffer.isBuffer(r.buffer) || r.buffer.length >= original.length) {
throw new Error('Expected a smaller cleaned buffer, got ' + r.buffer.length + ' vs original ' + original.length);
}
});
await check('mergeAndCompressPdf merges 2 files and reports page count', async () => {
const fs = require('fs');
const doc1 = fs.readFileSync('/tmp/test-fixtures/doc1.pdf');
const doc2 = fs.readFileSync('/tmp/test-fixtures/doc2.pdf');
const r = await presend.mergeAndCompressPdf([doc1, doc2]);
if (!Buffer.isBuffer(r.buffer) || r.headers['x-page-count'] !== '2') {
throw new Error('Got page count: ' + r.headers['x-page-count']);
}
});
// --- 9 new chained endpoints (v1.4.0) ---
await check('securityScan combines headers + reputation + attack_surface', async () => {
const r = await presend.securityScan('https://github.com');
if (typeof r.overall_score !== 'number' || !r.security_headers) {
throw new Error('Got: ' + JSON.stringify(r).slice(0, 200));
}
});
await check('emailSecurity parses SPF/DMARC for google.com', async () => {
const r = await presend.emailSecurity('google.com');
if (r.spf.configured !== true || r.dmarc.enforced !== true) {
throw new Error('Got: ' + JSON.stringify(r));
}
});
await check('imageSimilarity scores a recompressed image as near-identical', async () => {
const fs = require('fs');
const a = fs.readFileSync('/tmp/test-fixtures/similar_a.png');
const a2 = fs.readFileSync('/tmp/test-fixtures/similar_a2.jpg');
const r = await presend.imageSimilarity([a, a2]);
if (r.similarity_percent < 95) throw new Error('Got similarity: ' + r.similarity_percent);
});
await check('qrScan decodes a URL and flags is_url', async () => {
const fs = require('fs');
const buf = fs.readFileSync('/tmp/test-fixtures/qr_url.png');
const r = await presend.qrScan(buf);
if (r.found !== true || r.is_url !== true) throw new Error('Got: ' + JSON.stringify(r));
});
await check('fileType detects a mislabeled file', async () => {
const fs = require('fs');
const buf = fs.readFileSync('/tmp/test-fixtures/photo.png');
const r = await presend.fileType(buf, 'image/jpeg');
if (r.detected.mime !== 'image/png' || r.mismatch !== true) throw new Error('Got: ' + JSON.stringify(r));
});
await check('jwtVerify validates an HMAC-signed token', async () => {
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRlc3QgVXNlciIsImV4cCI6OTk5OTk5OTk5OX0.1yfGmVGJPGCw0Y4cxJg2kb8qBfeHst1CnxNFAeMVG_E';
const r = await presend.jwtVerify(token, { secret: 'mon-secret-de-test-tres-solide' });
if (r.valid !== true) throw new Error('Got: ' + JSON.stringify(r));
});
await check('malwareCheck computes SHA-256 and returns a structured result', async () => {
const fs = require('fs');
const buf = fs.readFileSync('/tmp/test-fixtures/doc1.pdf');
const r = await presend.malwareCheck(buf);
if (!r.sha256 || !('checked' in r.malwarebazaar)) throw new Error('Got: ' + JSON.stringify(r));
});
await check('textSimilarity scores identical text as 100%', async () => {
const t = 'This is exactly the same sentence repeated for a sanity check.';
const r = await presend.textSimilarity([t, t]);
if (r.similarity_percent !== 100) throw new Error('Got: ' + r.similarity_percent);
});
await check('aiCrawlerCheck returns a per-bot report', async () => {
const r = await presend.aiCrawlerCheck('github.com');
if (r.robots_txt_found !== true || !Array.isArray(r.bots) || r.bots.length === 0) {
throw new Error('Got: ' + JSON.stringify(r).slice(0, 200));
}
});
await check('dnsLookup returns A records for a real domain', async () => {
const r = await presend.dnsLookup('example.com', 'A');
if (!Array.isArray(r.records.A) || r.records.A.length === 0) {
throw new Error('Got: ' + JSON.stringify(r));
}
});
await check('dnsLookup with no type returns all 6 record types', async () => {
const r = await presend.dnsLookup('google.com');
const types = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS'];
const missing = types.filter(t => !(t in r.records));
if (missing.length > 0) throw new Error('Missing types: ' + missing.join(', '));
});
await check('whoisLookup returns registrar and age for a real domain', async () => {
const r = await presend.whoisLookup('github.com');
if (r.registered !== true || !r.registrar || typeof r.age_days !== 'number') {
throw new Error('Got: ' + JSON.stringify(r));
}
});
await check('whoisLookup reports registered:false for a fake domain', async () => {
const r = await presend.whoisLookup('this-does-not-exist-xyz789.com');
if (r.registered !== false) throw new Error('Got: ' + JSON.stringify(r));
});
await check('vulnerabilityCheck detects a known-vulnerable lodash version', async () => {
const r = await presend.vulnerabilityCheck('npm', 'lodash', '4.17.15');
if (r.vulnerable !== true || r.count === 0) throw new Error('Got: ' + JSON.stringify(r).slice(0, 200));
});
await check('vulnerabilityCheck reports a fake package as not found, never as clean', async () => {
const r = await presend.vulnerabilityCheck('npm', 'this-does-not-exist-xyz789', '1.0.0');
if (r.found !== false || r.vulnerable !== null) throw new Error('Got: ' + JSON.stringify(r));
});
await check('typosquatCheck flags a known typo of a popular package', async () => {
const r = await presend.typosquatCheck('npm', 'lodas');
if (r.suspicious !== true || r.similar_to.length === 0) throw new Error('Got: ' + JSON.stringify(r));
});
await check('typosquatCheck recognizes an exact well-known package name', async () => {
const r = await presend.typosquatCheck('npm', 'express');
if (r.is_known_popular_package !== true) throw new Error('Got: ' + JSON.stringify(r));
});
await check('ipReputation reports listed:true for a known DROP-listed IP', async () => {
const r = await presend.ipReputation('1.19.100.100');
if (r.listed !== true) throw new Error('Got: ' + JSON.stringify(r));
});
await check('ipReputation reports listed:false for a clean IP', async () => {
const r = await presend.ipReputation('8.8.8.8');
if (r.listed !== false) throw new Error('Got: ' + JSON.stringify(r));
});
await check('redirectTrace follows a known 3-hop redirect chain', async () => {
const r = await presend.redirectTrace('https://httpbin.org/redirect/3');
if (r.final_status !== 200 || r.hop_count < 3) throw new Error('Got: ' + JSON.stringify(r));
});
await check('repoHealthCheck returns real stars for a known repo', async () => {
const r = await presend.repoHealthCheck('lodash/lodash');
if (r.found !== true || typeof r.stars !== 'number' || r.stars < 1000) throw new Error('Got: ' + JSON.stringify(r));
});
// --- Endpoints ajoutés au client le 25 sept. 2026 (exemples repris d'openapi.json) ---
await check('maintainerChangeCheck', async () => { const r = await presend.maintainerChangeCheck('npm', 'lodash'); if (r.found !== true || typeof r.suspicious !== 'boolean' || !Array.isArray(r.ci_publisher_events)) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('maintainerChangeCheckBatch', async () => { const r = await presend.maintainerChangeCheckBatch(['lodash', 'express', 'presend-nonexistent-pkg-zz9']); if (r.count !== 3 || r.results[2].found !== false) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('typosquatCheckBatch', async () => { const r = await presend.typosquatCheckBatch('npm', ['lodas', 'ms', 'react']); if (r.count !== 3 || !r.results[0].suspicious || r.results[1].suspicious) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('typosquatCheckBatch (découpage 150 -> 2 appels)', async () => { const names = Array.from({ length: 150 }, (_, i) => 'pkg-' + i); const r = await presend.typosquatCheckBatch('npm', names); if (r.count !== 150 || r.results.length !== 150) throw new Error('count=' + r.count); });
await check('supplyChainCheck', async () => { const r = await presend.supplyChainCheck('npm', 'lodash'); if (!r || r.error) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('maintainerChangeCheck pypi', async () => { const r = await presend.maintainerChangeCheck('pypi', 'requests'); if (r.found !== true || r.maintainer_analysis !== 'npm_only' || typeof r.package_age_days !== 'number' || r.new_package !== false) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('maintainerChangeCheckBatch pypi', async () => { const r = await presend.maintainerChangeCheckBatch(['requests', 'presend-nonexistent-pkg-zz9'], 'pypi'); if (r.ecosystem !== 'pypi' || r.count !== 2 || r.results[0].found !== true || r.results[1].found !== false) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('supplyChainCheck version', async () => { const r = await presend.supplyChainCheck('npm', 'lodash', '4.17.15'); if (r.version_checked !== '4.17.15' || r.version_source !== 'requested') throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('cveLookup', async () => { const r = await presend.cveLookup("CVE-2021-44228"); if (!r || r.error) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('ibanValidate', async () => { const r = await presend.ibanValidate("DE89370400440532013000"); if (r.valid !== true) throw new Error(JSON.stringify(r).slice(0, 200)); });
await checkWithRetry('vatValidate (VIES, non bloquant)', async () => { const r = await presend.vatValidate("6388047V", "IE"); if (!r || r.error) throw new Error(JSON.stringify(r).slice(0, 200)); }, 3, true);
await check('linkMetadata', async () => { const r = await presend.linkMetadata('https://example.com'); if (!r || r.error) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('addressRisk', async () => { const r = await presend.addressRisk("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); if (!r || r.error) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('txDecode (vraie transaction Cosmos Hub)', async () => { const r = await presend.txDecode("CuYDCqEBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEngKLWNvc21vczFjNG16ajdhdTg3bm1qNHNzZWx4bGRwcWRrbjc5cHZ1ZXM2NHBtNxI0Y29zbW9zdmFsb3BlcjFmZjBkdzhrYXdzbnhrcmdqN3A2NWt2dzdqeHhha3lmOG41ODNneBoRCgV1YXRvbRIIMTAwMDc5OTgKnQEKJS9jb3Ntb3Muc3Rha2luZy52MWJldGExLk1zZ1VuZGVsZWdhdGUSdAotY29zbW9zMWM0bXpqN2F1ODdubWo0c3NlbHhsZHBxZGtuNzlwdnVlczY0cG03EjRjb3Ntb3N2YWxvcGVyMTAycnV2cHYyc3JtdW5mZmZ4YXZ0dHhuaGV6bG42Zm5jNTRhdDhjGg0KBXVhdG9tEgQyNTY1Cp8BCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnYKLWNvc21vczFjNG16ajdhdTg3bm1qNHNzZWx4bGRwcWRrbjc5cHZ1ZXM2NHBtNxI0Y29zbW9zdmFsb3BlcjEwd2xqeHBsMDMwNTNoOTY5MGFwbXllYWtseTN5bGhlanJ1Y3Z0bRoPCgV1YXRvbRIGMzQwMDAwEmgKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQI7ZbLGHK+7zfRSXqRGcxwFlGQywiNxHSw6x98l8IROyxIECgIIARgPEhQKDgoFdWF0b20SBTIwMDg1EInLehpA1osMdhghyuDtm5Fo/mcBsv+mtq11yzWtJitfbuy2v1MFxllKQZ2iifqkJmtVwpwcjCDh8+JtBFHuD4k60938EA=="); if (!r.body || !Array.isArray(r.body.messages) || !r.body.messages.length || r.body.messages[0].decoded !== true) throw new Error(JSON.stringify(r).slice(0, 200)); });
await check('rpcCheck', async () => { const r = await presend.rpcCheck("https://cosmos-rpc.publicnode.com"); if (!r || r.error) throw new Error(JSON.stringify(r).slice(0, 200)); });
console.log(`\n${passed} passed, ${failed} failed`);
if (failed > 0) process.exit(1);
}
main();