/** * Test script for Phase 6: Bulk CSV + Worker * Tests bulk processing functionality, CSV upload, and worker integration */ const http = require('http'); const fs = require('fs'); const path = require('path'); const BASE_URL = 'http://localhost:3333'; // Helper function to make HTTP requests function makeRequest(options, data = null) { return new Promise((resolve, reject) => { const req = http.request(options, (res) => { let body = ''; res.on('data', (chunk) => body += chunk); res.on('end', () => { try { const result = { statusCode: res.statusCode, headers: res.headers, body: res.headers['content-type'] && res.headers['content-type'].includes('application/json') ? JSON.parse(body) : body }; resolve(result); } catch (error) { resolve({ statusCode: res.statusCode, headers: res.headers, body: body }); } }); }); req.on('error', reject); if (data) { if (typeof data === 'string') { req.write(data); } else { req.write(JSON.stringify(data)); } } req.end(); }); } // Helper function to create test CSV file function createTestCSV() { const csvContent = `url,label,method,max_hops,enable_ssl https://httpbin.org/redirect/1,Test Redirect 1,GET,5,true https://httpbin.org/redirect/2,Test Redirect 2,GET,10,true https://example.com,Example Domain,GET,3,false https://httpbin.org/status/302,Status 302,GET,5,true invalid-url,Invalid URL,GET,5,true`; const filePath = path.join(__dirname, 'test-urls.csv'); fs.writeFileSync(filePath, csvContent); return filePath; } // Helper function to create multipart form data function createMultipartData(filePath, options = {}) { const boundary = '----formdata-test-' + Math.random().toString(36); const fileName = path.basename(filePath); const fileContent = fs.readFileSync(filePath); let data = ''; // Add file field data += `--${boundary}\r\n`; data += `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`; data += `Content-Type: text/csv\r\n\r\n`; data += fileContent; data += `\r\n`; // Add options field if (Object.keys(options).length > 0) { data += `--${boundary}\r\n`; data += `Content-Disposition: form-data; name="options"\r\n\r\n`; data += JSON.stringify(options); data += `\r\n`; } data += `--${boundary}--\r\n`; return { data: Buffer.from(data), boundary: boundary }; } async function runTests() { console.log('๐Ÿงช Starting Phase 6: Bulk CSV + Worker Tests\n'); let authToken = null; let testJobId = null; const csvFilePath = createTestCSV(); // Test 1: User Registration console.log('1๏ธโƒฃ Testing user registration...'); try { const registerResult = await makeRequest({ hostname: 'localhost', port: 3333, path: '/api/v1/auth/register', method: 'POST', headers: { 'Content-Type': 'application/json', }, }, { email: 'bulk-test@example.com', name: 'Bulk Test User', password: 'bulktest123', organizationName: 'Bulk Test Org' }); if (registerResult.statusCode === 201 && registerResult.body.success) { authToken = registerResult.body.data.token; console.log('โœ… User registration successful'); } else if (registerResult.statusCode === 409) { console.log('โ„น๏ธ User already exists, attempting login...'); // Try to login if user exists const loginResult = await makeRequest({ hostname: 'localhost', port: 3333, path: '/api/v1/auth/login', method: 'POST', headers: { 'Content-Type': 'application/json', }, }, { email: 'bulk-test@example.com', password: 'bulktest123' }); if (loginResult.statusCode === 200 && loginResult.body.success) { authToken = loginResult.body.data.token; console.log('โœ… User login successful'); } else { console.log('โŒ Login failed:', loginResult.body); return; } } else { console.log('โŒ Registration failed:', registerResult.body); return; } } catch (error) { console.log('โŒ Registration/login error:', error.message); return; } // Test 2: Get queue stats (should work before any jobs) console.log('\n2๏ธโƒฃ Testing queue statistics...'); try { const statsResult = await makeRequest({ hostname: 'localhost', port: 3333, path: '/api/v2/bulk/stats', method: 'GET', headers: { 'Authorization': `Bearer ${authToken}`, }, }); console.log('Queue stats response:', statsResult.statusCode); if (statsResult.statusCode === 200) { console.log('โœ… Queue stats retrieved:', statsResult.body.data); } else { console.log('โš ๏ธ Queue stats failed:', statsResult.body); } } catch (error) { console.log('โŒ Queue stats error:', error.message); } // Test 3: Create bulk job with JSON payload console.log('\n3๏ธโƒฃ Testing bulk job creation with JSON...'); try { const createJobResult = await makeRequest({ hostname: 'localhost', port: 3333, path: '/api/v2/bulk/jobs', method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, }, }, { urls: [ { url: 'https://httpbin.org/redirect/1', label: 'JSON Test 1' }, { url: 'https://example.com', label: 'JSON Test 2' } ], options: { method: 'GET', maxHops: 5, timeout: 10000, enableSSLAnalysis: true, enableSEOAnalysis: false, enableSecurityAnalysis: true } }); if (createJobResult.statusCode === 201 && createJobResult.body.success) { testJobId = createJobResult.body.data.jobId; console.log('โœ… Bulk job created:', testJobId); console.log('Job status:', createJobResult.body.data.status); console.log('URL count:', createJobResult.body.data.urls); } else { console.log('โŒ Bulk job creation failed:', createJobResult.body); } } catch (error) { console.log('โŒ Bulk job creation error:', error.message); } // Test 4: Upload CSV file console.log('\n4๏ธโƒฃ Testing CSV upload...'); try { const multipartData = createMultipartData(csvFilePath, { method: 'GET', maxHops: 8, timeout: 15000 }); const uploadResult = await makeRequest({ hostname: 'localhost', port: 3333, path: '/api/v2/bulk/upload', method: 'POST', headers: { 'Content-Type': `multipart/form-data; boundary=${multipartData.boundary}`, 'Authorization': `Bearer ${authToken}`, 'Content-Length': multipartData.data.length, }, }, multipartData.data); if (uploadResult.statusCode === 200 && uploadResult.body.success) { console.log('โœ… CSV upload successful'); console.log('Job ID:', uploadResult.body.data.jobId); console.log('Job status:', uploadResult.body.data.status); // Use this job for further tests if we don't have one from JSON if (!testJobId) { testJobId = uploadResult.body.data.jobId; } } else { console.log('โŒ CSV upload failed:', uploadResult.body); } } catch (error) { console.log('โŒ CSV upload error:', error.message); } // Test 5: Get user bulk jobs list console.log('\n5๏ธโƒฃ Testing bulk jobs list...'); try { const jobsListResult = await makeRequest({ hostname: 'localhost', port: 3333, path: '/api/v2/bulk/jobs?limit=10&offset=0', method: 'GET', headers: { 'Authorization': `Bearer ${authToken}`, }, }); if (jobsListResult.statusCode === 200 && jobsListResult.body.success) { console.log('โœ… Jobs list retrieved'); console.log('Job count:', jobsListResult.body.data.length); jobsListResult.body.data.forEach((job, index) => { console.log(` Job ${index + 1}: ${job.id} - ${job.status} (${job.urlCount} URLs)`); }); } else { console.log('โŒ Jobs list failed:', jobsListResult.body); } } catch (error) { console.log('โŒ Jobs list error:', error.message); } // Test 6: Get specific job details if (testJobId) { console.log('\n6๏ธโƒฃ Testing job details retrieval...'); try { const jobDetailResult = await makeRequest({ hostname: 'localhost', port: 3333, path: `/api/v2/bulk/jobs/${testJobId}`, method: 'GET', headers: { 'Authorization': `Bearer ${authToken}`, }, }); if (jobDetailResult.statusCode === 200 && jobDetailResult.body.success) { console.log('โœ… Job details retrieved'); console.log('Job status:', jobDetailResult.body.data.status); console.log('Progress:', jobDetailResult.body.data.progress); if (jobDetailResult.body.data.estimatedCompletionAt) { console.log('Estimated completion:', jobDetailResult.body.data.estimatedCompletionAt); } } else { console.log('โŒ Job details failed:', jobDetailResult.body); } } catch (error) { console.log('โŒ Job details error:', error.message); } // Test 7: Monitor job progress console.log('\n7๏ธโƒฃ Monitoring job progress...'); let attempts = 0; const maxAttempts = 30; // Wait up to 30 seconds while (attempts < maxAttempts) { try { const progressResult = await makeRequest({ hostname: 'localhost', port: 3333, path: `/api/v2/bulk/jobs/${testJobId}`, method: 'GET', headers: { 'Authorization': `Bearer ${authToken}`, }, }); if (progressResult.statusCode === 200 && progressResult.body.success) { const job = progressResult.body.data; console.log(`Progress: ${job.progress.processed}/${job.progress.total} (${job.status})`); if (job.status === 'completed' || job.status === 'failed') { console.log('โœ… Job completed!'); console.log('Final stats:', job.progress); if (job.results) { console.log('Results available:', job.results.length); } break; } } attempts++; if (attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second } } catch (error) { console.log('โŒ Progress monitoring error:', error.message); break; } } if (attempts >= maxAttempts) { console.log('โš ๏ธ Job monitoring timed out'); } // Test 8: Export results (if job completed) console.log('\n8๏ธโƒฃ Testing results export...'); try { const exportResult = await makeRequest({ hostname: 'localhost', port: 3333, path: `/api/v2/bulk/jobs/${testJobId}/export/csv`, method: 'GET', headers: { 'Authorization': `Bearer ${authToken}`, }, }); if (exportResult.statusCode === 200) { console.log('โœ… Results export successful'); console.log('Content-Type:', exportResult.headers['content-type']); console.log('File size:', exportResult.body.length, 'bytes'); } else { console.log('โš ๏ธ Results export failed:', exportResult.statusCode); if (typeof exportResult.body === 'object') { console.log('Error:', exportResult.body); } } } catch (error) { console.log('โŒ Results export error:', error.message); } } // Test 9: Test validation errors console.log('\n9๏ธโƒฃ Testing validation errors...'); try { // Test with invalid URL const invalidJobResult = await makeRequest({ hostname: 'localhost', port: 3333, path: '/api/v2/bulk/jobs', method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, }, }, { urls: [ { url: 'not-a-valid-url', label: 'Invalid URL' } ] }); if (invalidJobResult.statusCode === 400) { console.log('โœ… Validation correctly rejected invalid URL'); } else { console.log('โš ๏ธ Validation did not catch invalid URL:', invalidJobResult.body); } } catch (error) { console.log('โŒ Validation test error:', error.message); } // Test 10: Test unauthorized access console.log('\n๐Ÿ”Ÿ Testing unauthorized access...'); try { const unauthorizedResult = await makeRequest({ hostname: 'localhost', port: 3333, path: '/api/v2/bulk/jobs', method: 'GET', headers: { // No authorization header }, }); if (unauthorizedResult.statusCode === 401) { console.log('โœ… Unauthorized access correctly blocked'); } else { console.log('โš ๏ธ Unauthorized access was not blocked:', unauthorizedResult.statusCode); } } catch (error) { console.log('โŒ Unauthorized test error:', error.message); } // Cleanup try { fs.unlinkSync(csvFilePath); console.log('\n๐Ÿงน Test CSV file cleaned up'); } catch (error) { console.log('\nโš ๏ธ Failed to cleanup test file:', error.message); } console.log('\n๐ŸŽ‰ Phase 6 testing completed!'); console.log('\nKey features tested:'); console.log('โœ“ Bulk job creation with JSON payload'); console.log('โœ“ CSV file upload and parsing'); console.log('โœ“ Job progress monitoring'); console.log('โœ“ Results export to CSV'); console.log('โœ“ Queue statistics'); console.log('โœ“ Input validation'); console.log('โœ“ Authentication/authorization'); console.log('โœ“ Error handling'); } // Error handling process.on('uncaughtException', (error) => { console.log('\n๐Ÿ’ฅ Uncaught Exception:', error.message); process.exit(1); }); process.on('unhandledRejection', (reason) => { console.log('\n๐Ÿ’ฅ Unhandled Rejection:', reason); process.exit(1); }); // Run tests runTests().catch(error => { console.log('\n๐Ÿ’ฅ Test execution failed:', error.message); process.exit(1); });