Files
internify-be/test/test_certificate_template.js
2026-06-27 18:12:42 +07:00

479 lines
18 KiB
JavaScript

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();