syntropylog
Version:
An instance manager with observability for Node.js applications
169 lines • 6.46 kB
JavaScript
/*
* @file src/context/MockContextManager.ts
* @description Provides a mock implementation of the IContextManager interface,
* designed specifically for use in testing environments.
*/
import { randomUUID } from 'crypto';
/**
* @class MockContextManager
* @description A mock implementation of `IContextManager` for testing purposes.
* It uses a simple in-memory object instead of AsyncLocalStorage,
* making context management predictable and synchronous in tests.
* @implements {IContextManager}
*/
export class MockContextManager {
/** @private The in-memory key-value store for the context. */
store = {};
/** @private The HTTP header name used for the correlation ID. */
correlationIdHeader = 'x-correlation-id';
/** @private The HTTP header name used for the transaction ID. */
transactionIdHeader = 'x-trace-id';
/**
* Configures the mock context manager.
* @param options The configuration options.
* @param options.correlationIdHeader The custom header name to use for the correlation ID.
* @param options.transactionIdHeader The custom header name for the transaction ID.
*/
configure(options) {
if (options?.correlationIdHeader) {
this.correlationIdHeader = options.correlationIdHeader;
}
if (options?.transactionIdHeader) {
this.transactionIdHeader = options.transactionIdHeader;
}
}
/**
* Simulates running a function within a new, isolated context.
* It saves the current context, creates a new one inheriting the parent's values,
* runs the callback, and then restores the original context. This process
* correctly handles both synchronous and asynchronous callbacks.
* @template T The return type of the callback.
* @param {() => T} callback The function to execute within the new context.
* @returns {T} The result of the callback.
*/
async run(fn) {
// Deep-clone the original store to ensure true isolation.
const originalStore = JSON.parse(JSON.stringify(this.store));
this.store = { ...this.store }; // Inherit from parent for the current run.
try {
// Await the callback, which might be sync or async.
await fn();
}
finally {
// Always restore the original, unmodified context.
this.store = originalStore;
}
}
/**
* Gets a value from the mock context by its key.
* @template T The expected type of the value.
* @param {string} key The key of the value to retrieve.
* @returns The value, or `undefined` if not found.
*/
get(key) {
return this.store[key];
}
/**
* Gets a shallow copy of the entire mock context store.
* @returns {ContextData} An object containing all context data.
*/
getAll() {
// Return a shallow copy to prevent direct mutation of the internal store.
return { ...this.store };
}
/**
* Sets a key-value pair in the mock context.
* @param {string} key The key for the value.
* @param {ContextValue} value The value to store.
* @returns {void}
*/
set(key, value) {
this.store[key] = value;
}
/**
* Clears the in-memory store.
* Useful for resetting state between tests (e.g., in a `beforeEach` hook).
* @returns {void}
*/
clear() {
this.store = {};
}
/**
* A convenience method to get the correlation ID from the mock context.
* @returns {string} The correlation ID, or a generated one if not set.
*/
getCorrelationId() {
// Return the value from the configured header name to avoid duplication in logs.
let correlationId = this.get(this.correlationIdHeader);
if (!correlationId || typeof correlationId !== 'string') {
correlationId = randomUUID();
this.set(this.correlationIdHeader, correlationId);
}
return correlationId;
}
/**
* A convenience method to get the transaction ID from the mock context.
* @returns {string | undefined} The transaction ID, or undefined if not set.
*/
getTransactionId() {
// The key in the context is always 'transactionId'.
return this.get('transactionId');
}
/**
* A convenience method to set the transaction ID in the mock context.
* @param {string} transactionId The transaction ID to set.
*/
setTransactionId(transactionId) {
// The key in the context is always 'transactionId'.
this.set('transactionId', transactionId);
}
/**
* Gets the configured HTTP header name used for the correlation ID.
* @returns {string} The header name.
*/
getCorrelationIdHeaderName() {
return this.correlationIdHeader;
}
/**
* Gets the configured HTTP header name used for the transaction ID.
* @returns {string} The header name.
*/
getTransactionIdHeaderName() {
return this.transactionIdHeader;
}
/**
* Mock implementation for getting trace context headers.
* In a real tracing scenario, this would be populated.
* @returns `undefined` as this mock does not implement tracing.
*/
getTraceContextHeaders() {
const headers = {};
// Only include headers if we have an active context (store is not empty)
if (Object.keys(this.store).length === 0) {
return headers; // Return empty object if no context is active
}
const correlationId = this.getCorrelationId();
const transactionId = this.getTransactionId();
if (correlationId) {
headers[this.getCorrelationIdHeaderName()] = correlationId;
}
if (transactionId) {
headers[this.getTransactionIdHeaderName()] = transactionId;
}
return headers;
}
getFilteredContext() {
return this.getAll();
}
/**
* Reconfigures the logging matrix dynamically.
* This method allows changing which context fields are included in logs
* without affecting security configurations like masking or log levels.
* @param newMatrix The new logging matrix configuration
*/
reconfigureLoggingMatrix(newMatrix) {
// Mock implementation - no actual logging matrix in mock
// This is just to satisfy the interface
}
}
//# sourceMappingURL=MockContextManager.js.map