fix(tsmdb): add comprehensive unit tests for tsmdb components: checksum, query planner, index engine, session, and WAL
This commit is contained in:
361
test/test.tsmdb.session.ts
Normal file
361
test/test.tsmdb.session.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as smartmongo from '../ts/index.js';
|
||||
|
||||
const { SessionEngine } = smartmongo.tsmdb;
|
||||
|
||||
let sessionEngine: InstanceType<typeof SessionEngine>;
|
||||
|
||||
// ============================================================================
|
||||
// Setup
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: should create SessionEngine instance', async () => {
|
||||
sessionEngine = new SessionEngine({
|
||||
sessionTimeoutMs: 1000, // 1 second for testing
|
||||
cleanupIntervalMs: 10000, // 10 seconds to avoid cleanup during tests
|
||||
});
|
||||
expect(sessionEngine).toBeTruthy();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Session Lifecycle Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: startSession should create session with auto-generated ID', async () => {
|
||||
const session = sessionEngine.startSession();
|
||||
|
||||
expect(session).toBeTruthy();
|
||||
expect(session.id).toBeTruthy();
|
||||
expect(session.id.length).toBeGreaterThanOrEqual(32); // UUID hex string (32 or 36 with hyphens)
|
||||
expect(session.createdAt).toBeGreaterThan(0);
|
||||
expect(session.lastActivityAt).toBeGreaterThan(0);
|
||||
expect(session.inTransaction).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('session: startSession should create session with specified ID', async () => {
|
||||
const customId = 'custom-session-id-12345';
|
||||
const session = sessionEngine.startSession(customId);
|
||||
|
||||
expect(session.id).toEqual(customId);
|
||||
});
|
||||
|
||||
tap.test('session: startSession should create session with metadata', async () => {
|
||||
const metadata = { client: 'test-client', version: '1.0' };
|
||||
const session = sessionEngine.startSession(undefined, metadata);
|
||||
|
||||
expect(session.metadata).toBeTruthy();
|
||||
expect(session.metadata!.client).toEqual('test-client');
|
||||
expect(session.metadata!.version).toEqual('1.0');
|
||||
});
|
||||
|
||||
tap.test('session: getSession should return session by ID', async () => {
|
||||
const created = sessionEngine.startSession('get-session-test');
|
||||
const retrieved = sessionEngine.getSession('get-session-test');
|
||||
|
||||
expect(retrieved).toBeTruthy();
|
||||
expect(retrieved!.id).toEqual('get-session-test');
|
||||
expect(retrieved!.id).toEqual(created.id);
|
||||
});
|
||||
|
||||
tap.test('session: getSession should return undefined for non-existent session', async () => {
|
||||
const session = sessionEngine.getSession('non-existent-session-id');
|
||||
expect(session).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('session: touchSession should update lastActivityAt', async () => {
|
||||
const session = sessionEngine.startSession('touch-test-session');
|
||||
const originalLastActivity = session.lastActivityAt;
|
||||
|
||||
// Wait a bit to ensure time difference
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
const touched = sessionEngine.touchSession('touch-test-session');
|
||||
expect(touched).toBeTrue();
|
||||
|
||||
const updated = sessionEngine.getSession('touch-test-session');
|
||||
expect(updated!.lastActivityAt).toBeGreaterThanOrEqual(originalLastActivity);
|
||||
});
|
||||
|
||||
tap.test('session: touchSession should return false for non-existent session', async () => {
|
||||
const touched = sessionEngine.touchSession('non-existent-touch-session');
|
||||
expect(touched).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('session: endSession should remove the session', async () => {
|
||||
sessionEngine.startSession('end-session-test');
|
||||
expect(sessionEngine.getSession('end-session-test')).toBeTruthy();
|
||||
|
||||
const ended = await sessionEngine.endSession('end-session-test');
|
||||
expect(ended).toBeTrue();
|
||||
|
||||
expect(sessionEngine.getSession('end-session-test')).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('session: endSession should return false for non-existent session', async () => {
|
||||
const ended = await sessionEngine.endSession('non-existent-end-session');
|
||||
expect(ended).toBeFalse();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Session Expiry Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: isSessionExpired should return false for fresh session', async () => {
|
||||
const session = sessionEngine.startSession('fresh-session');
|
||||
const isExpired = sessionEngine.isSessionExpired(session);
|
||||
expect(isExpired).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('session: isSessionExpired should return true for old session', async () => {
|
||||
// Create a session with old lastActivityAt
|
||||
const session = sessionEngine.startSession('old-session');
|
||||
// Manually set lastActivityAt to old value (sessionTimeoutMs is 1000ms)
|
||||
(session as any).lastActivityAt = Date.now() - 2000;
|
||||
|
||||
const isExpired = sessionEngine.isSessionExpired(session);
|
||||
expect(isExpired).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('session: getSession should return undefined for expired session', async () => {
|
||||
const session = sessionEngine.startSession('expiring-session');
|
||||
// Manually expire the session
|
||||
(session as any).lastActivityAt = Date.now() - 2000;
|
||||
|
||||
const retrieved = sessionEngine.getSession('expiring-session');
|
||||
expect(retrieved).toBeUndefined();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Transaction Integration Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: startTransaction should mark session as in transaction', async () => {
|
||||
sessionEngine.startSession('txn-session-1');
|
||||
const started = sessionEngine.startTransaction('txn-session-1', 'txn-id-1', 1);
|
||||
|
||||
expect(started).toBeTrue();
|
||||
|
||||
const session = sessionEngine.getSession('txn-session-1');
|
||||
expect(session!.inTransaction).toBeTrue();
|
||||
expect(session!.txnId).toEqual('txn-id-1');
|
||||
expect(session!.txnNumber).toEqual(1);
|
||||
});
|
||||
|
||||
tap.test('session: startTransaction should return false for non-existent session', async () => {
|
||||
const started = sessionEngine.startTransaction('non-existent-txn-session', 'txn-id');
|
||||
expect(started).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('session: endTransaction should clear transaction state', async () => {
|
||||
sessionEngine.startSession('txn-session-2');
|
||||
sessionEngine.startTransaction('txn-session-2', 'txn-id-2');
|
||||
|
||||
const ended = sessionEngine.endTransaction('txn-session-2');
|
||||
expect(ended).toBeTrue();
|
||||
|
||||
const session = sessionEngine.getSession('txn-session-2');
|
||||
expect(session!.inTransaction).toBeFalse();
|
||||
expect(session!.txnId).toBeUndefined();
|
||||
expect(session!.txnNumber).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('session: endTransaction should return false for non-existent session', async () => {
|
||||
const ended = sessionEngine.endTransaction('non-existent-end-txn-session');
|
||||
expect(ended).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('session: getTransactionId should return transaction ID', async () => {
|
||||
sessionEngine.startSession('txn-id-session');
|
||||
sessionEngine.startTransaction('txn-id-session', 'my-txn-id');
|
||||
|
||||
const txnId = sessionEngine.getTransactionId('txn-id-session');
|
||||
expect(txnId).toEqual('my-txn-id');
|
||||
});
|
||||
|
||||
tap.test('session: getTransactionId should return undefined for session without transaction', async () => {
|
||||
sessionEngine.startSession('no-txn-session');
|
||||
const txnId = sessionEngine.getTransactionId('no-txn-session');
|
||||
expect(txnId).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('session: getTransactionId should return undefined for non-existent session', async () => {
|
||||
const txnId = sessionEngine.getTransactionId('non-existent-txn-id-session');
|
||||
expect(txnId).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('session: isInTransaction should return correct state', async () => {
|
||||
sessionEngine.startSession('in-txn-check-session');
|
||||
|
||||
expect(sessionEngine.isInTransaction('in-txn-check-session')).toBeFalse();
|
||||
|
||||
sessionEngine.startTransaction('in-txn-check-session', 'txn-check');
|
||||
expect(sessionEngine.isInTransaction('in-txn-check-session')).toBeTrue();
|
||||
|
||||
sessionEngine.endTransaction('in-txn-check-session');
|
||||
expect(sessionEngine.isInTransaction('in-txn-check-session')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('session: isInTransaction should return false for non-existent session', async () => {
|
||||
expect(sessionEngine.isInTransaction('non-existent-in-txn-session')).toBeFalse();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Session Listing Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: listSessions should return all active sessions', async () => {
|
||||
// Close and recreate to have a clean slate
|
||||
sessionEngine.close();
|
||||
sessionEngine = new SessionEngine({
|
||||
sessionTimeoutMs: 10000,
|
||||
cleanupIntervalMs: 60000,
|
||||
});
|
||||
|
||||
sessionEngine.startSession('list-session-1');
|
||||
sessionEngine.startSession('list-session-2');
|
||||
sessionEngine.startSession('list-session-3');
|
||||
|
||||
const sessions = sessionEngine.listSessions();
|
||||
expect(sessions.length).toEqual(3);
|
||||
});
|
||||
|
||||
tap.test('session: listSessions should not include expired sessions', async () => {
|
||||
const session = sessionEngine.startSession('expired-list-session');
|
||||
// Expire the session
|
||||
(session as any).lastActivityAt = Date.now() - 20000;
|
||||
|
||||
const sessions = sessionEngine.listSessions();
|
||||
const found = sessions.find(s => s.id === 'expired-list-session');
|
||||
expect(found).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('session: getSessionCount should return correct count', async () => {
|
||||
const count = sessionEngine.getSessionCount();
|
||||
expect(count).toBeGreaterThanOrEqual(3); // We created 3 sessions above
|
||||
});
|
||||
|
||||
tap.test('session: getSessionsWithTransactions should filter correctly', async () => {
|
||||
// Clean slate
|
||||
sessionEngine.close();
|
||||
sessionEngine = new SessionEngine({
|
||||
sessionTimeoutMs: 10000,
|
||||
cleanupIntervalMs: 60000,
|
||||
});
|
||||
|
||||
sessionEngine.startSession('no-txn-1');
|
||||
sessionEngine.startSession('no-txn-2');
|
||||
sessionEngine.startSession('with-txn-1');
|
||||
sessionEngine.startSession('with-txn-2');
|
||||
|
||||
sessionEngine.startTransaction('with-txn-1', 'txn-a');
|
||||
sessionEngine.startTransaction('with-txn-2', 'txn-b');
|
||||
|
||||
const txnSessions = sessionEngine.getSessionsWithTransactions();
|
||||
expect(txnSessions.length).toEqual(2);
|
||||
expect(txnSessions.every(s => s.inTransaction)).toBeTrue();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// getOrCreateSession Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: getOrCreateSession should create if missing', async () => {
|
||||
const session = sessionEngine.getOrCreateSession('get-or-create-new');
|
||||
expect(session).toBeTruthy();
|
||||
expect(session.id).toEqual('get-or-create-new');
|
||||
});
|
||||
|
||||
tap.test('session: getOrCreateSession should return existing session', async () => {
|
||||
const created = sessionEngine.startSession('get-or-create-existing');
|
||||
const retrieved = sessionEngine.getOrCreateSession('get-or-create-existing');
|
||||
|
||||
expect(retrieved.id).toEqual(created.id);
|
||||
expect(retrieved.createdAt).toEqual(created.createdAt);
|
||||
});
|
||||
|
||||
tap.test('session: getOrCreateSession should touch existing session', async () => {
|
||||
const session = sessionEngine.startSession('get-or-create-touch');
|
||||
const originalLastActivity = session.lastActivityAt;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
sessionEngine.getOrCreateSession('get-or-create-touch');
|
||||
const updated = sessionEngine.getSession('get-or-create-touch');
|
||||
|
||||
expect(updated!.lastActivityAt).toBeGreaterThanOrEqual(originalLastActivity);
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// extractSessionId Static Method Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: extractSessionId should handle UUID object', async () => {
|
||||
const { ObjectId } = smartmongo.tsmdb;
|
||||
const uuid = new smartmongo.tsmdb.plugins.bson.UUID();
|
||||
const lsid = { id: uuid };
|
||||
|
||||
const extracted = SessionEngine.extractSessionId(lsid);
|
||||
expect(extracted).toEqual(uuid.toHexString());
|
||||
});
|
||||
|
||||
tap.test('session: extractSessionId should handle string ID', async () => {
|
||||
const lsid = { id: 'string-session-id' };
|
||||
|
||||
const extracted = SessionEngine.extractSessionId(lsid);
|
||||
expect(extracted).toEqual('string-session-id');
|
||||
});
|
||||
|
||||
tap.test('session: extractSessionId should handle binary format', async () => {
|
||||
const binaryData = Buffer.from('test-binary-uuid', 'utf8').toString('base64');
|
||||
const lsid = { id: { $binary: { base64: binaryData } } };
|
||||
|
||||
const extracted = SessionEngine.extractSessionId(lsid);
|
||||
expect(extracted).toBeTruthy();
|
||||
expect(typeof extracted).toEqual('string');
|
||||
});
|
||||
|
||||
tap.test('session: extractSessionId should return undefined for null/undefined', async () => {
|
||||
expect(SessionEngine.extractSessionId(null)).toBeUndefined();
|
||||
expect(SessionEngine.extractSessionId(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('session: extractSessionId should return undefined for empty object', async () => {
|
||||
expect(SessionEngine.extractSessionId({})).toBeUndefined();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// refreshSession Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: refreshSession should update lastActivityAt', async () => {
|
||||
const session = sessionEngine.startSession('refresh-session-test');
|
||||
const originalLastActivity = session.lastActivityAt;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
const refreshed = sessionEngine.refreshSession('refresh-session-test');
|
||||
expect(refreshed).toBeTrue();
|
||||
|
||||
const updated = sessionEngine.getSession('refresh-session-test');
|
||||
expect(updated!.lastActivityAt).toBeGreaterThanOrEqual(originalLastActivity);
|
||||
});
|
||||
|
||||
tap.test('session: refreshSession should return false for non-existent session', async () => {
|
||||
const refreshed = sessionEngine.refreshSession('non-existent-refresh-session');
|
||||
expect(refreshed).toBeFalse();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Cleanup
|
||||
// ============================================================================
|
||||
|
||||
tap.test('session: close should clear all sessions', async () => {
|
||||
sessionEngine.startSession('close-test-session');
|
||||
expect(sessionEngine.getSessionCount()).toBeGreaterThan(0);
|
||||
|
||||
sessionEngine.close();
|
||||
|
||||
expect(sessionEngine.getSessionCount()).toEqual(0);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user