#!/usr/bin/env node /** * Document Upload Test * Tests universal document management system with storage provider selection */ const https = require('https'); const fs = require('fs'); const path = require('path'); const readline = require('readline'); const BASE_URL = 'https://kamau1-swiftops-backend.hf.space'; const ADMIN_EMAIL = 'lewis.kamau421@gmail.com'; const ADMIN_PASSWORD = 'TestPass123'; const LESLEY_EMAIL = 'lesley@example.com'; const TEST_IMAGE_PATH = path.join(__dirname, '../images/lesley.jpg'); let adminToken = null; let lesleyUserId = null; let documentId = null; let selectedProvider = null; const colors = { reset: '\x1b[0m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[36m', magenta: '\x1b[35m', cyan: '\x1b[96m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function makeRequest(method, path, data = null, token = null) { return new Promise((resolve, reject) => { const url = new URL(path, BASE_URL); const options = { method, headers: { 'Content-Type': 'application/json' }, timeout: 30000 }; if (token) { options.headers['Authorization'] = `Bearer ${token}`; } log(` → ${method} ${path}`, 'yellow'); const req = https.request(url, options, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { try { const response = { status: res.statusCode, data: body ? JSON.parse(body) : null }; log(` ← ${res.statusCode} ${res.statusMessage}`, 'yellow'); resolve(response); } catch (e) { log(` ← ${res.statusCode} (non-JSON)`, 'yellow'); resolve({ status: res.statusCode, data: body }); } }); }); req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); }); if (data) req.write(JSON.stringify(data)); req.end(); }); } function makeMultipartRequest(method, path, formData, token = null) { return new Promise((resolve, reject) => { const url = new URL(path, BASE_URL); const boundary = '----WebKitFormBoundary' + Math.random().toString(36).substring(2); const options = { method, headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}`, 'Authorization': `Bearer ${token}` }, timeout: 30000 }; log(` → ${method} ${path}`, 'yellow'); const req = https.request(url, options, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { try { const response = { status: res.statusCode, data: body ? JSON.parse(body) : null }; log(` ← ${res.statusCode} ${res.statusMessage}`, 'yellow'); resolve(response); } catch (e) { log(` ← ${res.statusCode} (non-JSON)`, 'yellow'); resolve({ status: res.statusCode, data: body }); } }); }); req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); }); // Build multipart form data let body = ''; for (const [key, value] of Object.entries(formData)) { if (key === 'file') { // File field body += `--${boundary}\r\n`; body += `Content-Disposition: form-data; name="file"; filename="${value.filename}"\r\n`; body += `Content-Type: ${value.contentType}\r\n\r\n`; body += value.content.toString('binary'); body += '\r\n'; } else { // Regular field body += `--${boundary}\r\n`; body += `Content-Disposition: form-data; name="${key}"\r\n\r\n`; body += value; body += '\r\n'; } } body += `--${boundary}--\r\n`; req.write(Buffer.from(body, 'binary')); req.end(); }); } async function step1_Login() { log('\n🔐 Step 1: Login as Admin', 'blue'); const response = await makeRequest('POST', '/api/v1/auth/login', { email: ADMIN_EMAIL, password: ADMIN_PASSWORD }); if (response.status === 200) { adminToken = response.data.access_token; log('✅ Login successful', 'green'); return true; } else { log(`❌ Login failed: ${response.status}`, 'red'); return false; } } async function step2_GetLesleyUserId() { log('\n🔍 Step 2: Get Lesley\'s User ID', 'blue'); const response = await makeRequest('GET', `/api/v1/users/search?email=${LESLEY_EMAIL}`, null, adminToken); if (response.status === 200 && response.data.length > 0) { lesleyUserId = response.data[0].id; log('✅ Found Lesley\'s user account', 'green'); log(` User ID: ${lesleyUserId}`); return true; } else { log('❌ Could not find Lesley\'s user account', 'red'); return false; } } async function step3_UploadDocument() { const providerName = selectedProvider === '1' ? 'Supabase' : 'Cloudinary'; log(`\n📤 Step 3: Upload Image to ${providerName}`, 'blue'); try { // BOTH options upload the SAME image (lesley.jpg) // This proves Supabase can handle images just like Cloudinary const fileBuffer = fs.readFileSync(TEST_IMAGE_PATH); const fileName = 'lesley.jpg'; const contentType = 'image/jpeg'; const documentType = 'profile_photo'; let description; if (selectedProvider === '1') { description = 'Profile photo uploaded to Supabase Storage (proving it can handle images)'; log(' 📸 Uploading image to Supabase Storage...', 'cyan'); } else { description = 'Profile photo uploaded to Cloudinary (optimized for images)'; log(' 📸 Uploading image to Cloudinary...', 'cyan'); } // Prepare form data const formData = { file: { filename: fileName, contentType: contentType, content: fileBuffer }, entity_type: 'user', entity_id: lesleyUserId, document_type: documentType, document_category: 'personal', description: description, tags: '["profile", "avatar", "test"]', is_public: 'false', force_provider: selectedProvider === '1' ? 'supabase' : 'cloudinary' }; const response = await makeMultipartRequest('POST', '/api/v1/documents/upload', formData, adminToken); if (response.status === 201) { documentId = response.data.id; log('✅ Document uploaded successfully', 'green'); log(` Document ID: ${documentId}`); log(` File: ${response.data.file_name}`); log(` Type: ${response.data.document_type}`); log(` Storage: ${response.data.storage_provider}`, 'magenta'); log(` URL: ${response.data.file_url.substring(0, 60)}...`); log(` Size: ${(response.data.file_size / 1024).toFixed(2)} KB`); // Verify correct storage provider const expectedProvider = selectedProvider === '1' ? 'supabase' : 'cloudinary'; if (response.data.storage_provider === expectedProvider) { log(` ✓ Routed to correct provider: ${expectedProvider}`, 'green'); } else { log(` ✗ Wrong provider! Expected ${expectedProvider}, got ${response.data.storage_provider}`, 'red'); } return true; } else { log(`❌ Upload failed: ${response.status}`, 'red'); if (response.data) { log(` ${JSON.stringify(response.data, null, 2)}`, 'red'); } return false; } } catch (error) { log(`❌ Upload error: ${error.message}`, 'red'); return false; } } async function step4_GetDocuments() { log('\n📄 Step 4: Get User Documents', 'blue'); const response = await makeRequest('GET', `/api/v1/documents/user/${lesleyUserId}`, null, adminToken); if (response.status === 200) { log('✅ Retrieved documents', 'green'); log(` Total Documents: ${response.data.total}`); if (response.data.documents.length > 0) { response.data.documents.forEach((doc, i) => { log(` ${i + 1}. ${doc.file_name} (${doc.document_type}) - ${doc.storage_provider}`); if (doc.uploader) { log(` Uploaded by: ${doc.uploader.name}`); } }); } return true; } else { log(`❌ Failed to get documents: ${response.status}`, 'red'); return false; } } async function step5_GetSingleDocument() { log('\n🔍 Step 5: Get Single Document', 'blue'); if (!documentId) { log('⚠️ No document ID available, skipping', 'yellow'); return true; } const response = await makeRequest('GET', `/api/v1/documents/id/${documentId}`, null, adminToken); if (response.status === 200) { log('✅ Retrieved document', 'green'); log(` File: ${response.data.file_name || 'N/A'}`); log(` Type: ${response.data.document_type || 'N/A'}`); log(` Category: ${response.data.document_category || 'N/A'}`); log(` Version: ${response.data.version || 'N/A'}`); if (response.data.tags && Array.isArray(response.data.tags)) { log(` Tags: ${response.data.tags.join(', ')}`); } else { log(` Tags: ${response.data.tags || 'N/A'}`); } return true; } else { log(`❌ Failed to get document: ${response.status}`, 'red'); return false; } } async function step6_UpdateDocumentMetadata() { log('\n✏️ Step 6: Update Document Metadata', 'blue'); if (!documentId) { log('⚠️ No document ID available, skipping', 'yellow'); return true; } const response = await makeRequest('PUT', `/api/v1/documents/id/${documentId}`, { description: 'Updated profile photo for Lesley', tags: ['profile', 'avatar', 'updated'], is_public: true }, adminToken); if (response.status === 200) { log('✅ Document metadata updated', 'green'); log(` Description: ${response.data.description}`); log(` Tags: ${response.data.tags.join(', ')}`); return true; } else { log(`❌ Failed to update metadata: ${response.status}`, 'red'); return false; } } function promptStorageProvider() { return new Promise((resolve) => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); console.log('\n' + '='.repeat(70)); console.log('📦 Storage Provider Selection'); console.log('='.repeat(70)); console.log(''); console.log('Choose storage provider to test:'); console.log(''); console.log(' 1️⃣ Supabase Storage'); console.log(' • General-purpose storage (can handle ANY file type)'); console.log(' • Test: Upload lesley.jpg to Supabase'); console.log(''); console.log(' 2️⃣ Cloudinary'); console.log(' • Optimized for images/videos (CDN, transformations)'); console.log(' • Test: Upload lesley.jpg to Cloudinary'); console.log(''); console.log('📸 Both options upload the SAME image (lesley.jpg)'); console.log(' This proves Supabase can handle images just like Cloudinary!'); console.log(''); rl.question('Enter your choice (1 or 2): ', (answer) => { rl.close(); const choice = answer.trim(); if (choice === '1' || choice === '2') { resolve(choice); } else { console.log('\n❌ Invalid choice. Please run again and select 1 or 2.'); process.exit(1); } }); }); } async function runTest() { // Prompt for storage provider selectedProvider = await promptStorageProvider(); const providerName = selectedProvider === '1' ? 'Supabase Storage' : 'Cloudinary'; log('\n' + '='.repeat(70), 'blue'); log('📁 Document Upload Test', 'blue'); log(` Provider: ${providerName}`, 'magenta'); log(` File: lesley.jpg (image/jpeg)`, 'cyan'); log(` Goal: Prove ${providerName} can handle images`, 'cyan'); log('='.repeat(70), 'blue'); const results = []; // Setup if (!await step1_Login()) { log('\n❌ Cannot continue without login', 'red'); process.exit(1); } results.push({ name: 'Login', status: true }); if (!await step2_GetLesleyUserId()) { log('\n❌ Cannot continue without user ID', 'red'); process.exit(1); } results.push({ name: 'Get User ID', status: true }); // Document Tests results.push({ name: 'Upload Document', status: await step3_UploadDocument() }); results.push({ name: 'Get Documents', status: await step4_GetDocuments() }); results.push({ name: 'Get Single Document', status: await step5_GetSingleDocument() }); results.push({ name: 'Update Metadata', status: await step6_UpdateDocumentMetadata() }); // Summary log('\n' + '='.repeat(70), 'blue'); log('📊 Test Summary', 'blue'); log('='.repeat(70), 'blue'); let passed = 0, failed = 0; results.forEach(r => { const icon = r.status ? '✅' : '❌'; const color = r.status ? 'green' : 'red'; log(`${icon} ${r.name}`, color); if (r.status) passed++; else failed++; }); log('\n' + '-'.repeat(70)); log(`Total: ${results.length} | Passed: ${passed} | Failed: ${failed}`); if (failed === 0) { const providerName = selectedProvider === '1' ? 'Supabase Storage' : 'Cloudinary'; log('\n🎉 All tests passed!', 'green'); log(`\n✨ Document system is working perfectly!`, 'magenta'); log(` Storage Provider: ${providerName}`, 'magenta'); log(` User ID: ${lesleyUserId}`, 'magenta'); if (documentId) { log(` Document ID: ${documentId}`, 'magenta'); } log(`\n💡 Tip: Run the test again and select the other provider!`, 'cyan'); } else { log(`\n⚠️ ${failed} step(s) failed`, 'red'); } } runTest().catch(error => { log(`\n💥 Fatal error: ${error.message}`, 'red'); console.error(error); process.exit(1); });