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 runTests() { console.log('=== STARTING INTEGRATION TESTS FOR CERTIFICATE TEMPLATE UPLOAD ===\n'); let adminToken = ''; let mentor1Token = ''; let mentor2Token = ''; let internToken = ''; let project1Id = null; let project2Id = null; const password = 'password123'; const internEmail = `new_intern_cert_${Date.now()}@internify.com`; const getHeader = (token) => ({ headers: { Authorization: `Bearer ${token}` } }); const getMultipartHeader = (token, boundary) => ({ headers: { Authorization: `Bearer ${token}`, 'Content-Type': `multipart/form-data; boundary=${boundary}` } }); try { // 1. Login as 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; console.log(' Admin logged in successfully!\n'); // 2. Logging in as Mentors console.log('2. Logging in as Mentors...'); const mentor1LoginRes = await axios.post(`${BASE_URL}/auth-api/login`, { email: 'mentor1@internify.com', password: password }); mentor1Token = mentor1LoginRes.data.data.token; const mentor2LoginRes = await axios.post(`${BASE_URL}/auth-api/login`, { email: 'mentor2@internify.com', password: password }); mentor2Token = mentor2LoginRes.data.data.token; console.log(' Mentors logged in successfully!\n'); // 3. Registering a new Intern to assign to Mentor 1 Project console.log(`3. Registering a new Intern (${internEmail})...`); // Create a new vacancy with an image upload to get a valid UUID id_lowongan_magang const lwnBoundary = '----WebKitFormBoundaryLowonganUpload'; const lwnFields = { posisi: 'Web Developer Test', kelompok_peminatan: 'Software Engineering', jobdesk: 'Testing task and certificate templates.', 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); console.log(' Creating lowongan magang (vacancy) dynamically...'); const createLwnRes = await axios.post( `${BASE_URL}/lowongan-magang-api/add`, lwnPayload, getMultipartHeader(adminToken, lwnBoundary) ); const lowonganId = createLwnRes.data.data.id; console.log(' Vacancy created successfully! ID:', lowonganId); // Submit application (lamaran) using the add-mobile endpoint which also registers the student console.log(' Submitting internship application (which registers the student profile)...'); const appBoundary = '----WebKitFormBoundaryApplicationSubmission'; const appFields = { nama_depan: 'Intern', nama_belakang: 'Cert', email: internEmail, 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; console.log(' Application submitted successfully! Student ID:', idMahasiswa); // Fetch all lamaran to get the lamaran ID console.log(' Retrieving lamaran list to find the newly created lamaran ID...'); const listLamaranRes = await axios.get(`${BASE_URL}/lamaran-magang-api/get?limit=100`, getHeader(adminToken)); const lamaranList = listLamaranRes.data.data; const lamaranItem = lamaranList.find(l => l.id_mahasiswa === idMahasiswa); if (!lamaranItem) { throw new Error('TEST FAILED: Created lamaran not found in lamaran list!'); } const lamaranId = lamaranItem.id; console.log(' Found Lamaran ID:', lamaranId); // Accept application to create user account console.log(' Accepting application to generate Intern user account...'); await axios.patch(`${BASE_URL}/lamaran-magang-api/update/${lamaranId}`, { status: 'diterima' }, getHeader(adminToken)); // Wait a brief moment for the user insertion to complete await new Promise(resolve => setTimeout(resolve, 1000)); // Force override user's password in the database directly console.log(' Overriding intern user password in DB directly to password123...'); const hashedPassword = await bcrypt.hash('password123', 10); await prisma.user.update({ where: { email: internEmail }, data: { password: hashedPassword } }); // Login as new Intern const internLoginRes = await axios.post(`${BASE_URL}/auth-api/login`, { email: internEmail, password: 'password123' }); internToken = internLoginRes.data.data.token; console.log(' Intern logged in successfully!\n'); // 4. Mentors create projects dynamically to establish ownership console.log('4. Creating projects dynamically for Mentors...'); // Mentor 1 creates Project 1 and assigns Intern (newly registered) const project1Res = await axios.post(`${BASE_URL}/project-api/add`, { project_icon: 'code', project_name: `Mentor 1 Project - ${Date.now()}`, description: 'Owned by Mentor 1.', start_date: '2026-07-01', end_date: '2026-10-01', member_emails: [internEmail] }, getHeader(mentor1Token)); project1Id = project1Res.data.data.id; // Mentor 2 creates Project 2 const project2Res = await axios.post(`${BASE_URL}/project-api/add`, { project_icon: 'shield', project_name: `Mentor 2 Project - ${Date.now()}`, description: 'Owned by Mentor 2.', start_date: '2026-07-01', end_date: '2026-10-01', member_emails: [] }, getHeader(mentor2Token)); project2Id = project2Res.data.data.id; console.log(` Projects created: Project 1 ID = ${project1Id}, Project 2 ID = ${project2Id}\n`); // Prepare dummy file contents for upload const uploadBoundary = '----WebKitFormBoundaryTemplateUpload'; const dummyTemplateContent = Buffer.from('fake-png-template-content'); const uploadPayload = createMultipartPayload(uploadBoundary, {}, { template: { name: 'cert_template.png', type: 'image/png', content: dummyTemplateContent } }); // 5. Mentor 1 uploads template to Project 1 (Should succeed) console.log(`5. Mentor 1 uploading certificate template to Project 1...`); const uploadRes = await axios.post( `${BASE_URL}/certificate-api/projects/${project1Id}/template`, uploadPayload, getMultipartHeader(mentor1Token, uploadBoundary) ); console.log(' Response status:', uploadRes.status); console.log(' Uploaded template path:', uploadRes.data.data.certificate_template); if (!uploadRes.data.data.certificate_template.startsWith('/uploads/')) { throw new Error('TEST FAILED: Uploaded template path does not start with /uploads/'); } console.log(' PASSED: Mentor 1 successfully uploaded template to their own project.\n'); // 6. Mentor 2 tries to upload to Project 1 (Should fail - 403) console.log(`6. Testing isolation: Mentor 2 trying to upload template to Project 1 (owned by Mentor 1)...`); try { await axios.post( `${BASE_URL}/certificate-api/projects/${project1Id}/template`, uploadPayload, getMultipartHeader(mentor2Token, uploadBoundary) ); throw new Error('TEST FAILED: Mentor 2 was able to upload template to Project 1!'); } catch (err) { if (err.response && err.response.status === 403) { console.log(' PASSED: Access denied with 403 Forbidden as expected.\n'); } else { throw err; } } // 7. Admin uploads template to Project 1 (Should succeed) console.log(`7. Admin uploading certificate template to Project 1...`); const adminUploadRes = await axios.post( `${BASE_URL}/certificate-api/projects/${project1Id}/template`, uploadPayload, getMultipartHeader(adminToken, uploadBoundary) ); console.log(' Response status:', adminUploadRes.status); console.log(' PASSED: Admin successfully uploaded template to project.\n'); // 8. Intern tries to upload template to Project 1 (Should fail - 401/403) console.log(`8. Testing role restriction: Intern trying to upload template to Project 1...`); try { await axios.post( `${BASE_URL}/certificate-api/projects/${project1Id}/template`, uploadPayload, getMultipartHeader(internToken, uploadBoundary) ); throw new Error('TEST FAILED: Intern was able to upload template to Project 1!'); } catch (err) { if (err.response && (err.response.status === 403 || err.response.status === 401)) { console.log(` PASSED: Access denied with ${err.response.status} as expected.\n`); } else { throw err; } } // 9. Intern retrieves project details and verifies certificate template path is returned console.log(`9. Intern retrieving Project 1 details to verify certificate template exposure...`); const projectDetailRes = await axios.get(`${BASE_URL}/project-api/get/${project1Id}`, getHeader(internToken)); console.log(' Project detail certificate_template:', projectDetailRes.data.data.certificate_template); if (!projectDetailRes.data.data.certificate_template) { throw new Error('TEST FAILED: Project detail response does not include certificate_template!'); } console.log(' PASSED: Intern successfully retrieved project detail containing certificate template URL.\n'); // 10. Claim Certificate Tests console.log('10. Claim Certificate Tests...'); // Get Intern User ID const internUser = await prisma.user.findUnique({ where: { email: internEmail } }); const internUserId = internUser.id; console.log(` Intern User ID: ${internUserId}`); // A. Claim before tasks are created (Should fail - 400) console.log(' A. Trying to claim certificate before any tasks are created...'); try { await axios.post( `${BASE_URL}/certificate-api/claim`, { id_project: project1Id }, getHeader(internToken) ); throw new Error('TEST FAILED: Intern claimed certificate with no tasks!'); } catch (err) { if (err.response && err.response.status === 400) { console.log(' PASSED: Claim failed with 400 Bad Request as expected.'); console.log(' Error Message:', err.response.data.message); } else { throw err; } } // B. Create a task for Project 1 console.log(' B. Creating a task for Project 1...'); const task = await prisma.task.create({ data: { id_project: project1Id, slug: `final-assignment-${Date.now()}`, title: 'Final Assignment', description: 'Submit your final report.', deadline_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now submission_type: 'url_link' } }); console.log(` Task created successfully! ID: ${task.id}`); // C. Claim before submitting tasks (Should fail - 400) console.log(' C. Trying to claim certificate before task submission...'); try { await axios.post( `${BASE_URL}/certificate-api/claim`, { id_project: project1Id }, getHeader(internToken) ); throw new Error('TEST FAILED: Intern claimed certificate without submitting tasks!'); } catch (err) { if (err.response && err.response.status === 400) { console.log(' PASSED: Claim failed with 400 Bad Request as expected.'); console.log(' Error Message:', err.response.data.message); } else { throw err; } } // D. Submit the task console.log(' D. Creating submission for the task...'); const submission = await prisma.taskSubmission.create({ data: { id_task: task.id, id_user: internUserId, url_link: 'https://github.com/internify/report' } }); console.log(` Submission created successfully! ID: ${submission.id}`); // E. Claim certificate (Should succeed - 201) console.log(' E. Claiming certificate after submitting tasks...'); const claimRes = await axios.post( `${BASE_URL}/certificate-api/claim`, { id_project: project1Id }, getHeader(internToken) ); console.log(' Response status:', claimRes.status); console.log(' Claimed Certificate No:', claimRes.data.data.certificate_no); if (claimRes.status !== 201) { throw new Error(`TEST FAILED: Expected status 201, got ${claimRes.status}`); } console.log(' PASSED: Certificate claimed successfully!\n'); // F. Claim certificate again (Should fail - 409) console.log(' F. Trying to claim certificate again...'); try { await axios.post( `${BASE_URL}/certificate-api/claim`, { id_project: project1Id }, getHeader(internToken) ); throw new Error('TEST FAILED: Intern claimed the same certificate twice!'); } catch (err) { if (err.response && err.response.status === 409) { console.log(' PASSED: Claim failed with 409 Conflict as expected.'); console.log(' Error Message:', err.response.data.message); } else { throw err; } } // G. Retrieve my certificates (Should include the claimed certificate) console.log(' G. Verifying certificate is in my certificates list...'); const myCertsRes = await axios.get( `${BASE_URL}/certificate-api/my-certificates`, getHeader(internToken) ); console.log(' Certificates list length:', myCertsRes.data.data.length); const foundCert = myCertsRes.data.data.find(c => c.id_project === project1Id); if (!foundCert) { throw new Error('TEST FAILED: Claimed certificate not found in my certificates list!'); } console.log(' PASSED: Claimed certificate verified in list!\n'); // 11. Assign Member Restriction Tests console.log('11. Assign Member Restriction Tests...'); // A. Assign Intern (who is active in Project 1) to Project 2 (Should fail - 409) console.log(' A. Trying to assign intern (already active in Project 1) to Project 2...'); try { await axios.post( `${BASE_URL}/project-api/assign-member`, { id_project: project2Id, id_user: internUserId }, getHeader(mentor2Token) ); throw new Error('TEST FAILED: Intern was assigned to two active projects!'); } catch (err) { if (err.response && err.response.status === 409) { console.log(' PASSED: Assign failed with 409 Conflict as expected.'); console.log(' Error Message:', err.response.data.message); } else { throw err; } } // B. Remove intern from Project 1 console.log(' B. Removing intern from Project 1...'); const removeRes = await axios.post( `${BASE_URL}/project-api/remove-member`, { id_project: project1Id, id_user: internUserId }, getHeader(mentor1Token) ); console.log(' Response status:', removeRes.status); if (removeRes.status !== 200) { throw new Error(`TEST FAILED: Failed to remove member, got status ${removeRes.status}`); } console.log(' PASSED: Intern successfully removed from Project 1.'); // C. Assign intern to Project 2 (Should succeed now) console.log(' C. Assigning intern to Project 2...'); const assignRes = await axios.post( `${BASE_URL}/project-api/assign-member`, { id_project: project2Id, id_user: internUserId }, getHeader(mentor2Token) ); console.log(' Response status:', assignRes.status); if (assignRes.status !== 200) { throw new Error(`TEST FAILED: Failed to assign member, got status ${assignRes.status}`); } console.log(' PASSED: Intern successfully assigned to Project 2.'); // D. Assign intern to Project 2 again (Should fail - 409) console.log(' D. Trying to assign intern to Project 2 again...'); try { await axios.post( `${BASE_URL}/project-api/assign-member`, { id_project: project2Id, id_user: internUserId }, getHeader(mentor2Token) ); throw new Error('TEST FAILED: Intern assigned to Project 2 twice!'); } catch (err) { if (err.response && err.response.status === 409) { console.log(' PASSED: Assign failed with 409 Conflict as expected.'); console.log(' Error Message:', err.response.data.message); } else { throw err; } } console.log('=== ALL INTEGRATION TESTS PASSED SUCCESSFULLY! ==='); await prisma.$disconnect(); process.exit(0); } catch (err) { console.error('=== 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();