feat(source2): push all latest files - device labeling, help system, proxy docs, database isolation fix
- Added DOKUMENTASI-FILTER-PER-SITE.md (site isolation docs) - Fixed start-with-env.js to force-load .env.production - Fixed MONGODB_URI hostname from mongodb-netify to mongodb.prod.proit.id - Updated .gitignore to exclude sensitive scripts and credential files - Minor UI and labeling improvements
This commit is contained in:
1 parent
a403f752f3
commit
dd4c8f6876
385 files changed
+31016
-8268
No files matched your search
@@ -0,0 +1,197 @@
|
||||
// test/sync_test.js
|
||||
// TDD Integration Test: Verifikasi sinkronisasi 100% file antara Localhost dan Domain
|
||||
'use strict';
|
||||
|
||||
const { Client } = require('ssh2');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// Load environment variables if available
|
||||
try {
|
||||
require('dotenv').config({ path: path.join(__dirname, '../.env.local') });
|
||||
} catch (e) {}
|
||||
|
||||
const SSH = {
|
||||
host: '103.185.47.52',
|
||||
port: 2222,
|
||||
username: 'adminbackend',
|
||||
password: 'htEo7x6LsBQiEHHH',
|
||||
readyTimeout: 60000
|
||||
};
|
||||
|
||||
const REMOTE_ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
|
||||
const LOCAL_ROOT = path.join(__dirname, '..');
|
||||
|
||||
let testCount = 0;
|
||||
let passedCount = 0;
|
||||
|
||||
function assert(label, condition) {
|
||||
testCount++;
|
||||
if (condition) {
|
||||
console.log(` ✓ PASSED: ${label}`);
|
||||
passedCount++;
|
||||
} else {
|
||||
console.error(` ✗ FAILED: ${label}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getMD5(filePath) {
|
||||
try {
|
||||
const data = fs.readFileSync(filePath);
|
||||
return crypto.createHash('md5').update(data).digest('hex');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getFilesLocal(dir, files = []) {
|
||||
const list = fs.readdirSync(dir);
|
||||
for (const item of list) {
|
||||
const fullPath = path.join(dir, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
if (stat.isDirectory()) {
|
||||
if (item !== 'node_modules' && item !== '.next' && item !== '.git') {
|
||||
getFilesLocal(fullPath, files);
|
||||
}
|
||||
} else {
|
||||
files.push(path.relative(LOCAL_ROOT, fullPath).replace(/\\/g, '/'));
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
const remoteScript = `
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const root = '/home/adminbackend/web/demoplace.my.id/public_html';
|
||||
|
||||
function getMD5(filePath) {
|
||||
try {
|
||||
const data = fs.readFileSync(filePath);
|
||||
return crypto.createHash('md5').update(data).digest('hex');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getFilesRemote(dir, files = {}) {
|
||||
try {
|
||||
const list = fs.readdirSync(dir);
|
||||
for (const item of list) {
|
||||
const fullPath = path.join(dir, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
if (stat.isDirectory()) {
|
||||
if (item !== 'node_modules' && item !== '.next' && item !== '.git') {
|
||||
getFilesRemote(fullPath, files);
|
||||
}
|
||||
} else {
|
||||
const rel = path.relative(root, fullPath);
|
||||
files[rel] = { md5: getMD5(fullPath), size: stat.size };
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
return files;
|
||||
}
|
||||
|
||||
const filesMap = {};
|
||||
getFilesRemote(path.join(root, 'src'), filesMap);
|
||||
getFilesRemote(path.join(root, 'public'), filesMap);
|
||||
getFilesRemote(path.join(root, 'backend'), filesMap);
|
||||
getFilesRemote(path.join(root, 'proxy'), filesMap);
|
||||
|
||||
const rootConfigs = ['package.json', 'tsconfig.json', 'next.config.ts', '.env.production'];
|
||||
for (const f of rootConfigs) {
|
||||
const full = path.join(root, f);
|
||||
if (fs.existsSync(full)) {
|
||||
const stat = fs.statSync(full);
|
||||
filesMap[f] = { md5: getMD5(full), size: stat.size };
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(filesMap));
|
||||
`;
|
||||
|
||||
async function runTests() {
|
||||
console.log('\n=========================================');
|
||||
console.log(' RUNNING TDD SYNC COMPARISON TESTS');
|
||||
console.log('=========================================\n');
|
||||
|
||||
let remoteFiles = {};
|
||||
try {
|
||||
const conn = new Client();
|
||||
const remoteFilesStr = await new Promise((resolve, reject) => {
|
||||
conn.on('ready', () => {
|
||||
const b64 = Buffer.from(remoteScript).toString('base64');
|
||||
conn.exec(`echo "${b64}" | base64 -d | node`, (err, stream) => {
|
||||
if (err) return reject(err);
|
||||
let out = '';
|
||||
let errOut = '';
|
||||
stream.on('data', d => out += d);
|
||||
stream.stderr.on('data', d => errOut += d);
|
||||
stream.on('close', () => {
|
||||
conn.end();
|
||||
if (errOut.trim()) {
|
||||
reject(new Error(errOut));
|
||||
} else {
|
||||
resolve(out);
|
||||
}
|
||||
});
|
||||
});
|
||||
}).on('error', reject).connect(SSH);
|
||||
});
|
||||
|
||||
remoteFiles = JSON.parse(remoteFilesStr);
|
||||
console.log('✓ Connected to remote server and retrieved remote file list.');
|
||||
} catch (err) {
|
||||
console.error('✗ Failed to connect to remote server:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get local files
|
||||
const localFiles = {};
|
||||
const pathsToCheck = ['src', 'public', 'backend', 'proxy'];
|
||||
for (const p of pathsToCheck) {
|
||||
const full = path.join(LOCAL_ROOT, p);
|
||||
if (fs.existsSync(full)) {
|
||||
const files = getFilesLocal(full);
|
||||
for (const f of files) {
|
||||
const localPath = path.join(LOCAL_ROOT, f);
|
||||
localFiles[f] = { md5: getMD5(localPath) };
|
||||
}
|
||||
}
|
||||
}
|
||||
const rootConfigs = ['package.json', 'tsconfig.json', 'next.config.ts', '.env.production'];
|
||||
for (const f of rootConfigs) {
|
||||
const full = path.join(LOCAL_ROOT, f);
|
||||
if (fs.existsSync(full)) {
|
||||
localFiles[f] = { md5: getMD5(full) };
|
||||
}
|
||||
}
|
||||
|
||||
// Perform assertions
|
||||
for (const [relPath, info] of Object.entries(remoteFiles)) {
|
||||
const localInfo = localFiles[relPath];
|
||||
assert(`File "${relPath}" should exist locally`, !!localInfo);
|
||||
if (localInfo) {
|
||||
assert(`File "${relPath}" content should match remote exactly`, localInfo.md5 === info.md5);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n=========================================`);
|
||||
console.log(` RESULT: ${passedCount} / ${testCount} tests passed.`);
|
||||
console.log(`=========================================\n`);
|
||||
|
||||
if (passedCount !== testCount) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
runTests().catch(e => {
|
||||
console.error('[FATAL]', e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in new issue
Block a user