UNPKG

@wgtechlabs/log-engine

Version:

A lightweight, security-first logging utility with automatic data redaction for Node.js applications - the first logging library with built-in PII protection.

231 lines 7.71 kB
/** * Test utilities for handling async file operations properly * Replaces arbitrary timeouts with proper Promise-based waiting * Optimized for CI environments with faster polling and shorter timeouts */ import * as fs from 'fs'; import * as path from 'path'; // Optimized defaults for CI environments const DEFAULT_TIMEOUT = 3000; // Reduced from 5000ms const POLL_INTERVAL = 5; // Reduced from 10ms for faster detection /** * Wait for a file to exist with optimized polling */ export async function waitForFile(filePath, timeoutMs = DEFAULT_TIMEOUT) { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { try { await fs.promises.access(filePath, fs.constants.F_OK); return; // File exists } catch (error) { // File doesn't exist yet, wait a bit await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); } } throw new Error(`File ${filePath} did not appear within ${timeoutMs}ms`); } /** * Wait for multiple files to exist with parallel checking */ export async function waitForFiles(filePaths, timeoutMs = DEFAULT_TIMEOUT) { await Promise.all(filePaths.map(filePath => waitForFile(filePath, timeoutMs))); } /** * Wait for a file to have specific content with optimized polling */ export async function waitForFileContent(filePath, expectedContent, timeoutMs = DEFAULT_TIMEOUT) { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { try { const content = await fs.promises.readFile(filePath, 'utf8'); if (typeof expectedContent === 'string') { if (content.includes(expectedContent)) { return; } } else { if (expectedContent.test(content)) { return; } } } catch (error) { // File might not exist yet or be readable } await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); } throw new Error(`File ${filePath} did not contain expected content within ${timeoutMs}ms`); } /** * Wait for a directory to be empty with faster polling */ export async function waitForDirectoryEmpty(dirPath, timeoutMs = DEFAULT_TIMEOUT) { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { try { const files = await fs.promises.readdir(dirPath); if (files.length === 0) { return; } } catch (error) { // Directory might not exist, which is also "empty" return; } await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL)); } throw new Error(`Directory ${dirPath} was not empty within ${timeoutMs}ms`); } /** * Safely remove a file with optimized retry logic */ export async function safeRemoveFile(filePath, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { await fs.promises.unlink(filePath); return; } catch (error) { if (i === maxRetries - 1) { // Only throw on last retry if it's not a "file not found" error if (error.code !== 'ENOENT') { throw error; } } else { // Wait a bit before retrying (reduced wait time) await new Promise(resolve => setTimeout(resolve, 10)); } } } } /** * Safely clean up a directory with retry logic */ export async function safeCleanupDirectory(dirPath) { try { const files = await fs.promises.readdir(dirPath); // Remove all files with retry logic await Promise.all(files.map(file => safeRemoveFile(path.join(dirPath, file)))); // Remove the directory itself await fs.promises.rmdir(dirPath); } catch (error) { // Directory might not exist, which is fine if (error.code !== 'ENOENT') { // Try force removal as fallback try { await fs.promises.rm(dirPath, { recursive: true, force: true }); } catch (fallbackError) { // Ignore cleanup errors in tests } } } } /** * Enhanced mock HTTP handler with faster timeouts and better error handling */ export class MockHttpHandler { constructor() { this.requests = []; this.pendingPromises = []; this.timeoutIds = new Set(); } addRequest(url, options) { let resolveRequest; const promise = new Promise(resolve => { resolveRequest = resolve; }); this.requests.push({ url, options, resolve: resolveRequest }); this.pendingPromises.push(promise); } getRequests() { return this.requests.map(({ url, options }) => ({ url, options })); } async waitForRequests(count = 1, timeoutMs = DEFAULT_TIMEOUT) { const startTime = Date.now(); // Set up a timeout that will be cleaned up let timeoutId; const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error(`Expected ${count} requests but got ${this.requests.length} within ${timeoutMs}ms`)); }, timeoutMs); this.timeoutIds.add(timeoutId); }); try { while (this.requests.length < count && Date.now() - startTime < timeoutMs) { await new Promise(resolve => { const id = setTimeout(resolve, POLL_INTERVAL); this.timeoutIds.add(id); }); } if (this.requests.length < count) { throw new Error(`Expected ${count} requests but got ${this.requests.length} within ${timeoutMs}ms`); } // Mark all requests as processed this.requests.forEach(req => req.resolve()); // Wait for all pending promises to resolve await Promise.all(this.pendingPromises); } finally { // Clean up the timeout if (timeoutId) { clearTimeout(timeoutId); this.timeoutIds.delete(timeoutId); } } } clear() { // Clean up any remaining timeouts for (const timeoutId of this.timeoutIds) { clearTimeout(timeoutId); } this.timeoutIds.clear(); this.requests = []; this.pendingPromises = []; } } /** * Create a test timeout that fails fast instead of hanging */ export function createTestTimeout(timeoutMs = DEFAULT_TIMEOUT) { let timeoutId; const promise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error(`Test timed out after ${timeoutMs}ms`)); }, timeoutMs); }); const cancel = () => { if (timeoutId) { clearTimeout(timeoutId); } }; return { promise, cancel }; } /** * Race a promise against a timeout for fail-fast behavior */ export async function withTimeout(promise, timeoutMs = DEFAULT_TIMEOUT) { const timeout = createTestTimeout(timeoutMs); try { const result = await Promise.race([ promise, timeout.promise ]); // Cancel the timeout since we got a result timeout.cancel(); return result; } catch (error) { // Cancel the timeout in case of error timeout.cancel(); throw error; } } //# sourceMappingURL=async-test-utils.js.map