swiftops-backend / tests /integration /test_document_upload.js
kamau1's picture
feat: signed urls for viewing
986dd29
#!/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);
});