const axios = require('axios'); const bcrypt = require('bcrypt'); const { PrismaClient } = require('../src/generated/prisma'); const BASE_URL = 'http://localhost:9000'; const prisma = new PrismaClient(); function createMultipartPayload(boundary, fields, files) { const chunks = []; for (const [key, value] of Object.entries(fields)) { if (value !== undefined && value !== null) { chunks.push(Buffer.from(`--${boundary}\r\n` + `Content-Disposition: form-data; name="${key}"\r\n\r\n` + `${value}\r\n`)); } } for (const [key, file] of Object.entries(files)) { if (file) { chunks.push(Buffer.from(`--${boundary}\r\n` + `Content-Disposition: form-data; name="${key}"; filename="${file.name}"\r\n` + `Content-Type: ${file.type}\r\n\r\n`)); chunks.push(file.content); chunks.push(Buffer.from('\r\n')); } } chunks.push(Buffer.from(`--${boundary}--\r\n`)); return Buffer.concat(chunks); } async function registerIntern(adminToken, email, firstName, lastName) { // Create lowongan magang const lwnBoundary = '----WebKitFormBoundaryLowonganUpload'; const lwnFields = { posisi: `Web Developer Test - ${Date.now()}`, kelompok_peminatan: 'Software Engineering', jobdesk: 'Testing batch certificate generation.', lokasi: 'Remote', kualifikasi: 'NodeJS', benefit: 'Certificate', durasi_awal: '2026-07-01', durasi_akhir: '2026-10-01', paid: 'unpaid' }; const lwnFiles = { image: { name: 'poster.png', type: 'image/png', content: Buffer.from('fake-image-data') } }; const lwnPayload = createMultipartPayload(lwnBoundary, lwnFields, lwnFiles); const createLwnRes = await axios.post( `${BASE_URL}/lowongan-magang-api/add`, lwnPayload, { headers: { Authorization: `Bearer ${adminToken}`, 'Content-Type': `multipart/form-data; boundary=${lwnBoundary}` } } ); const lowonganId = createLwnRes.data.data.id; // Submit application const appBoundary = '----WebKitFormBoundaryApplicationSubmission'; const appFields = { nama_depan: firstName, nama_belakang: lastName, email: email, kontak: '08123456789', jurusan: 'Computer Science', universitas: 'Intern University', negara: 'Indonesia', motivasi: 'I want to learn.', relevant_skills: 'NodeJS, React' }; const appFiles = { cv: { name: 'cv.pdf', type: 'application/pdf', content: Buffer.from('fake-pdf-cv') }, portofolio: { name: 'portfolio.pdf', type: 'application/pdf', content: Buffer.from('fake-pdf-portfolio') } }; const appPayload = createMultipartPayload(appBoundary, appFields, appFiles); const applyRes = await axios.post( `${BASE_URL}/lamaran-magang-api/add-mobile/${lowonganId}`, appPayload, { headers: { 'Content-Type': `multipart/form-data; boundary=${appBoundary}` } } ); const idMahasiswa = applyRes.data.data.id_mahasiswa; // Wait a brief moment await new Promise(resolve => setTimeout(resolve, 500)); // Find lamaran ID const listLamaranRes = await axios.get(`${BASE_URL}/lamaran-magang-api/get?limit=100`, { headers: { Authorization: `Bearer ${adminToken}` } }); const lamaranItem = listLamaranRes.data.data.find(l => l.id_mahasiswa === idMahasiswa); if (!lamaranItem) { throw new Error(`Failed to find lamaran for student ID: ${idMahasiswa}`); } const lamaranId = lamaranItem.id; // Accept application await axios.patch(`${BASE_URL}/lamaran-magang-api/update/${lamaranId}`, { status: 'diterima' }, { headers: { Authorization: `Bearer ${adminToken}` } }); await new Promise(resolve => setTimeout(resolve, 1000)); // Force override password const hashedPassword = await bcrypt.hash('password123', 10); await prisma.user.update({ where: { email }, data: { password: hashedPassword } }); // Login const loginRes = await axios.post(`${BASE_URL}/auth-api/login`, { email, password: 'password123' }); const user = await prisma.user.findUnique({ where: { email } }); return { userId: user.id, token: loginRes.data.data.token }; } async function runTests() { console.log('=== STARTING BATCH GENERATE CERTIFICATE TESTS ===\n'); let adminToken = ''; let mentor1Token = ''; let mentor2Token = ''; let project1Id = null; const password = 'password123'; const intern1Email = `intern1_batch_${Date.now()}@internify.com`; const intern2Email = `intern2_batch_${Date.now()}@internify.com`; const getHeader = (token) => ({ headers: { Authorization: `Bearer ${token}` } }); try { // 1. Login Admin console.log('1. Logging in as Admin (admin1@internify.com)...'); const adminLoginRes = await axios.post(`${BASE_URL}/auth-api/login`, { email: 'admin1@internify.com', password: password }); adminToken = adminLoginRes.data.data.token; // 2. Login Mentors console.log('2. Logging in as Mentors...'); const mentor1Login = await axios.post(`${BASE_URL}/auth-api/login`, { email: 'mentor1@internify.com', password }); mentor1Token = mentor1Login.data.data.token; const mentor2Login = await axios.post(`${BASE_URL}/auth-api/login`, { email: 'mentor2@internify.com', password }); mentor2Token = mentor2Login.data.data.token; // 3. Register Intern 1 and Intern 2 console.log('3. Registering Intern 1 & Intern 2...'); const intern1 = await registerIntern(adminToken, intern1Email, 'InternOne', 'Batch'); console.log(` Registered Intern 1: User ID ${intern1.userId}`); const intern2 = await registerIntern(adminToken, intern2Email, 'InternTwo', 'Batch'); console.log(` Registered Intern 2: User ID ${intern2.userId}`); // 4. Create Project owned by Mentor 1 and assign both interns console.log('4. Creating Project under Mentor 1 with both interns...'); const createProjectRes = await axios.post(`${BASE_URL}/project-api/add`, { project_icon: 'code', project_name: `Batch Certificate Project - ${Date.now()}`, description: 'Project to test batch generation.', start_date: '2026-07-01', end_date: '2026-10-01', member_emails: [intern1Email, intern2Email] }, getHeader(mentor1Token)); project1Id = createProjectRes.data.data.id; console.log(` Project created successfully! ID: ${project1Id}`); // 5. Create a task in the project console.log('5. Creating a required project task...'); const task = await prisma.task.create({ data: { id_project: project1Id, slug: `required-task-${Date.now()}`, title: 'Project Submission', description: 'Complete project tasks.', deadline_at: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000), submission_type: 'url_link' } }); console.log(` Task created! ID: ${task.id}`); // 6. Submit task for Intern 1 only console.log('6. Submitting task for Intern 1 only...'); await prisma.taskSubmission.create({ data: { id_task: task.id, id_user: intern1.userId, url_link: 'https://github.com/intern1/solution' } }); console.log(' Intern 1 task submitted successfully. Intern 2 has NO submissions.'); // 7. Test Authorization: Call generate endpoint using Intern 1 token (Should fail - 401) console.log('\n--- API TEST 1: Intern calls generate endpoint (Should fail - 401) ---'); try { await axios.post(`${BASE_URL}/certificate-api/generate`, { id_project: project1Id, id_users: [intern1.userId] }, getHeader(intern1.token)); throw new Error('TEST FAILED: Intern generated certificate!'); } catch (err) { if (err.response && err.response.status === 401) { console.log(' PASSED: Denied access to intern with 401 Unauthorized.'); } else { throw err; } } // 8. Test Authorization: Call generate endpoint using Mentor 2 token (Should fail - 403) console.log('\n--- API TEST 2: Mentor 2 (non-owner) calls generate endpoint (Should fail - 403) ---'); try { await axios.post(`${BASE_URL}/certificate-api/generate`, { id_project: project1Id, id_users: [intern1.userId] }, getHeader(mentor2Token)); throw new Error('TEST FAILED: Unauthorized mentor generated certificate!'); } catch (err) { if (err.response && err.response.status === 403) { console.log(' PASSED: Denied access to unauthorized mentor with 403 Forbidden.'); } else { throw err; } } // 9. Test Eligibility: Request generation for both Intern 1 (completed) and Intern 2 (incomplete) (Should fail - 400) console.log('\n--- API TEST 3: Generate batch containing ineligible intern (Should fail - 400) ---'); try { await axios.post(`${BASE_URL}/certificate-api/generate`, { id_project: project1Id, id_users: [intern1.userId, intern2.userId] }, getHeader(mentor1Token)); throw new Error('TEST FAILED: Generated certificate for batch containing ineligible intern!'); } catch (err) { if (err.response && err.response.status === 400) { console.log(' PASSED: Rejected batch due to ineligible intern with 400 Bad Request.'); console.log(' Error Message:', err.response.data.message); } else { throw err; } } // 10. Success case: Generate certificate for Intern 1 only (Should succeed - 201) console.log('\n--- API TEST 4: Generate certificate for eligible Intern 1 (Should succeed - 201) ---'); const genRes = await axios.post(`${BASE_URL}/certificate-api/generate`, { id_project: project1Id, id_users: [intern1.userId] }, getHeader(mentor1Token)); console.log(' Response status:', genRes.status); if (genRes.status !== 201) { throw new Error(`TEST FAILED: Expected 201 Created, got ${genRes.status}`); } console.log(' Generated Certificate No:', genRes.data.data.certificates[0].certificate_no); console.log(' PASSED: Certificate generated successfully.'); // 11. Conflict check: Try to generate for Intern 1 again (Should fail - 409) console.log('\n--- API TEST 5: Re-generate certificate for Intern 1 (Should fail - 409) ---'); try { await axios.post(`${BASE_URL}/certificate-api/generate`, { id_project: project1Id, id_users: [intern1.userId] }, getHeader(mentor1Token)); throw new Error('TEST FAILED: Re-generated certificate for Intern 1!'); } catch (err) { if (err.response && err.response.status === 409) { console.log(' PASSED: Blocked duplicate generation with 409 Conflict.'); } else { throw err; } } // 12. Verify Project Details mapping: Check task submissions and certificates in details response console.log('\n--- API TEST 6: Fetch project details and verify database mapping ---'); const projectDetailsRes = await axios.get(`${BASE_URL}/project-api/get/${project1Id}`, getHeader(mentor1Token)); const projectData = projectDetailsRes.data.data; // Verify certificates list exists if (!projectData.certificates || projectData.certificates.length !== 1) { throw new Error('TEST FAILED: Certificates list in project details is missing or incorrect!'); } console.log(' Found Certificates in Project details:', projectData.certificates); // Verify task submissions exist const projectTask = projectData.tasks.find(t => t.id === task.id); if (!projectTask || !projectTask.submissions || projectTask.submissions.length !== 1) { throw new Error('TEST FAILED: Task submissions list in project details is missing or incorrect!'); } console.log(' Found Submissions in Project Task:', projectTask.submissions); console.log(' PASSED: Database mapping checked successfully.'); console.log('\n=== ALL BATCH GENERATE CERTIFICATE TESTS PASSED SUCCESSFULLY! ==='); await prisma.$disconnect(); process.exit(0); } catch (err) { console.error('\n=== TEST EXECUTION ENCOUNTERED AN ERROR ==='); if (err.response) { console.error(`Status: ${err.response.status}`); console.error('Response Data:', JSON.stringify(err.response.data, null, 2)); } else { console.error(err); } await prisma.$disconnect(); process.exit(1); } } runTests();