Spaces:
Sleeping
Sleeping
| /** | |
| * 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); | |
| }); | |