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,239 @@
|
||||
// Test suite for the inactivity timeout logic (TDD approach)
|
||||
// This script mocks React hooks and browser APIs to verify correct behavior of our session timeout logic.
|
||||
|
||||
const assert = require("assert");
|
||||
|
||||
console.log("=== RUNNING INACTIVITY TIMEOUT LOGIC TESTS ===");
|
||||
|
||||
// ─── Test Harness ─────────────────────────────────────────────────────────────
|
||||
class MockDocument {
|
||||
constructor() {
|
||||
this.visibilityState = "visible";
|
||||
this.listeners = {};
|
||||
}
|
||||
addEventListener(event, callback) {
|
||||
if (!this.listeners[event]) this.listeners[event] = [];
|
||||
this.listeners[event].push(callback);
|
||||
}
|
||||
removeEventListener(event, callback) {
|
||||
if (!this.listeners[event]) return;
|
||||
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
||||
}
|
||||
trigger(event) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach(cb => cb());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockWindow {
|
||||
constructor() {
|
||||
this.listeners = {};
|
||||
this.location = { href: "" };
|
||||
}
|
||||
addEventListener(event, callback) {
|
||||
if (!this.listeners[event]) this.listeners[event] = [];
|
||||
this.listeners[event].push(callback);
|
||||
}
|
||||
removeEventListener(event, callback) {
|
||||
if (!this.listeners[event]) return;
|
||||
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
||||
}
|
||||
trigger(event) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach(cb => cb());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Test Inactivity Logic Class ──────────────────────────────────────────────
|
||||
class InactivityTimeoutTester {
|
||||
constructor(options = {}) {
|
||||
this.timeoutSeconds = options.timeoutSeconds || 3600; // 1 hour
|
||||
this.warningSeconds = options.warningSeconds || 120; // 2 minutes
|
||||
this.showWarning = false;
|
||||
this.timeLeft = this.warningSeconds;
|
||||
this.isLoggedOut = false;
|
||||
this.lastActivity = Date.now();
|
||||
this.visibilityState = "visible";
|
||||
}
|
||||
|
||||
// Simulate user activity event
|
||||
onUserActivity(currentTime) {
|
||||
if (this.visibilityState === "visible") {
|
||||
this.lastActivity = currentTime || Date.now();
|
||||
this.showWarning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate tick (every second or arbitrary delay)
|
||||
tick(currentTime) {
|
||||
const elapsed = Math.floor((currentTime - this.lastActivity) / 1000);
|
||||
const remaining = this.timeoutSeconds - elapsed;
|
||||
|
||||
if (remaining <= 0) {
|
||||
this.isLoggedOut = true;
|
||||
this.showWarning = false;
|
||||
} else if (remaining <= this.warningSeconds) {
|
||||
this.timeLeft = remaining;
|
||||
this.showWarning = true;
|
||||
} else {
|
||||
this.showWarning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate returning to the tab
|
||||
onVisibilityChange(newVisibilityState, currentTime) {
|
||||
this.visibilityState = newVisibilityState;
|
||||
if (newVisibilityState === "visible") {
|
||||
const elapsed = Math.floor((currentTime - this.lastActivity) / 1000);
|
||||
const remaining = this.timeoutSeconds - elapsed;
|
||||
|
||||
if (remaining <= 0) {
|
||||
this.isLoggedOut = true;
|
||||
this.showWarning = false;
|
||||
} else if (remaining <= this.warningSeconds) {
|
||||
this.timeLeft = remaining;
|
||||
this.showWarning = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── TEST CASES ───────────────────────────────────────────────────────────────
|
||||
|
||||
function testResetActivityWhenVisible() {
|
||||
console.log("- Test: Reset activity timer when tab is visible");
|
||||
const tester = new InactivityTimeoutTester();
|
||||
const initialActivity = tester.lastActivity;
|
||||
|
||||
// Wait 10 seconds
|
||||
const timeAfter10s = initialActivity + 10000;
|
||||
tester.onUserActivity(timeAfter10s);
|
||||
|
||||
// Last activity should be updated
|
||||
assert.ok(tester.lastActivity >= initialActivity);
|
||||
}
|
||||
|
||||
function testNoResetActivityWhenHidden() {
|
||||
console.log("- Test: Do NOT reset activity timer when tab is hidden");
|
||||
const tester = new InactivityTimeoutTester();
|
||||
const initialActivity = tester.lastActivity;
|
||||
|
||||
// Change tab status to hidden
|
||||
tester.visibilityState = "hidden";
|
||||
|
||||
// User activity should be ignored for reset
|
||||
tester.onUserActivity(initialActivity + 10000);
|
||||
assert.strictEqual(tester.lastActivity, initialActivity);
|
||||
}
|
||||
|
||||
function testWarningThreshold() {
|
||||
console.log("- Test: Trigger warning at 2 minutes remaining");
|
||||
const tester = new InactivityTimeoutTester({ timeoutSeconds: 3600, warningSeconds: 120 });
|
||||
const start = Date.now();
|
||||
|
||||
// Tick at 59 minutes (3540 seconds elapsed, 60 seconds remaining)
|
||||
const warnTime = start + (3540 * 1000);
|
||||
tester.tick(warnTime);
|
||||
|
||||
assert.strictEqual(tester.showWarning, true);
|
||||
assert.strictEqual(tester.timeLeft, 60);
|
||||
}
|
||||
|
||||
function testSafeZoneAfterActivity() {
|
||||
console.log("- Test: Warning goes away when user becomes active");
|
||||
const tester = new InactivityTimeoutTester({ timeoutSeconds: 3600, warningSeconds: 120 });
|
||||
const start = Date.now();
|
||||
|
||||
// Tick at 3540 seconds (warning is shown)
|
||||
const warnTime = start + (3540 * 1000);
|
||||
tester.tick(warnTime);
|
||||
assert.strictEqual(tester.showWarning, true);
|
||||
|
||||
// User performs activity at warnTime
|
||||
tester.onUserActivity(warnTime);
|
||||
|
||||
// Tick immediately after activity (should be in safe zone)
|
||||
tester.tick(warnTime);
|
||||
assert.strictEqual(tester.showWarning, false);
|
||||
}
|
||||
|
||||
function testAutoLogout() {
|
||||
console.log("- Test: Auto-logout after 1 hour of total inactivity");
|
||||
const tester = new InactivityTimeoutTester({ timeoutSeconds: 3600, warningSeconds: 120 });
|
||||
const start = Date.now();
|
||||
|
||||
// Tick after 3601 seconds
|
||||
const expireTime = start + (3601 * 1000);
|
||||
tester.tick(expireTime);
|
||||
|
||||
assert.strictEqual(tester.isLoggedOut, true);
|
||||
assert.strictEqual(tester.showWarning, false);
|
||||
}
|
||||
|
||||
function testTabSwitchSilentlyTicks() {
|
||||
console.log("- Test: Switching tabs continues counting down silently");
|
||||
const tester = new InactivityTimeoutTester({ timeoutSeconds: 3600, warningSeconds: 120 });
|
||||
const start = Date.now();
|
||||
|
||||
// Switch tab away to hidden
|
||||
tester.onVisibilityChange("hidden", start);
|
||||
|
||||
// Tick 10 minutes (600s) later while hidden
|
||||
const tenMinsLater = start + (600 * 1000);
|
||||
tester.tick(tenMinsLater);
|
||||
|
||||
// Should still be in safe zone (no warning yet, no logout)
|
||||
assert.strictEqual(tester.showWarning, false);
|
||||
assert.strictEqual(tester.isLoggedOut, false);
|
||||
}
|
||||
|
||||
function testTabReturnImmediateCheck() {
|
||||
console.log("- Test: Tab return triggers immediate state check");
|
||||
const tester = new InactivityTimeoutTester({ timeoutSeconds: 3600, warningSeconds: 120 });
|
||||
const start = Date.now();
|
||||
|
||||
// User switches tab away
|
||||
tester.onVisibilityChange("hidden", start);
|
||||
|
||||
// User is away for 59 minutes (3540s), then returns
|
||||
const returnTime = start + (3540 * 1000);
|
||||
tester.onVisibilityChange("visible", returnTime);
|
||||
|
||||
// Should immediately show warning with remaining time of 60 seconds
|
||||
assert.strictEqual(tester.showWarning, true);
|
||||
assert.strictEqual(tester.timeLeft, 60);
|
||||
}
|
||||
|
||||
function testTabReturnExpiredLogout() {
|
||||
console.log("- Test: Tab return logs out immediately if expired while away");
|
||||
const tester = new InactivityTimeoutTester({ timeoutSeconds: 3600, warningSeconds: 120 });
|
||||
const start = Date.now();
|
||||
|
||||
// User switches tab away
|
||||
tester.onVisibilityChange("hidden", start);
|
||||
|
||||
// User is away for 61 minutes (3660s), then returns
|
||||
const returnTime = start + (3660 * 1000);
|
||||
tester.onVisibilityChange("visible", returnTime);
|
||||
|
||||
// Should immediately trigger logout
|
||||
assert.strictEqual(tester.isLoggedOut, true);
|
||||
}
|
||||
|
||||
// Execute tests
|
||||
try {
|
||||
testResetActivityWhenVisible();
|
||||
testNoResetActivityWhenHidden();
|
||||
testWarningThreshold();
|
||||
testSafeZoneAfterActivity();
|
||||
testAutoLogout();
|
||||
testTabSwitchSilentlyTicks();
|
||||
testTabReturnImmediateCheck();
|
||||
testTabReturnExpiredLogout();
|
||||
console.log("✓ ALL TESTS PASSED SUCCESSFULLY!");
|
||||
} catch (error) {
|
||||
console.error("❌ TEST FAILURE:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in new issue
Block a user