Files
Deep-Package-Inspection/test/test-inactivity.js
T
rafif dd4c8f6876 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
2026-07-29 14:14:29 +07:00

240 lines
7.9 KiB
JavaScript

// 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);
}