From e49fb45faed38f49f7e49e0279dfd826abc57992 Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Tue, 23 Dec 2025 17:09:49 -0700 Subject: [PATCH 1/5] Add subpath import patterns #src and #js --- package.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/package.json b/package.json index e98717952..143b453cd 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,22 @@ "test:unit": "TSX_TSCONFIG_PATH=./unitTests/tsconfig.json npx mocha --config unitTests/.mocharc.json", "test:unit:all": "npm run test:unit unitTests" }, + "imports": { + "#src/*": { + "import": "./*.ts", + "require": { + "typestrip": "./*.ts", + "default": "./dist/*.js" + } + }, + "#js/*": { + "import": "./*.js", + "require": { + "typestrip": "./*.js", + "default": "./dist/*.js" + } + } + }, "engines": { "node": ">=20", "minimum-node": "20.0.0" From e6f123aba98865bdae7ef0e8efbaa665f4c56932 Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Tue, 23 Dec 2025 17:13:47 -0700 Subject: [PATCH 2/5] Redo component unit test migration in JS ...with subpath import patterns --- package.json | 2 +- unitTests/.mocharc.json | 4 +- .../{Component.test.ts => Component.test.js} | 6 +- ...omponentV1.test.ts => ComponentV1.test.js} | 24 ++- ...ryHandler.test.ts => EntryHandler.test.js} | 19 +- ...Watcher.test.ts => OptionsWatcher.test.js} | 22 +-- .../{Scope.test.ts => Scope.test.js} | 73 +++----- ...Loader.test.ts => componentLoader.test.js} | 63 ++++--- ...est.ts => deriveCommonPatternBase.test.js} | 5 +- ...ption.test.ts => deriveGlobOption.test.js} | 5 +- ...Restart.test.ts => requestRestart.test.js} | 5 +- ...ath.test.ts => resolveBaseURLPath.test.js} | 5 +- ...Status.test.ts => ComponentStatus.test.js} | 84 ++++----- ...est.ts => ComponentStatusRegistry.test.js} | 173 +++++++++--------- .../status/{api.test.ts => api.test.js} | 51 +++--- ...rossThread.test.ts => crossThread.test.js} | 68 ++++--- .../status/{errors.test.ts => errors.test.js} | 44 ++--- .../status/{index.test.ts => index.test.js} | 60 +++--- .../{registry.test.ts => registry.test.js} | 26 +-- unitTests/components/waitFor.js | 14 ++ unitTests/components/waitFor.ts | 12 -- unitTests/testUtils.js | 82 +++++++++ unitTests/tsconfig.json | 13 -- 23 files changed, 442 insertions(+), 418 deletions(-) rename unitTests/components/{Component.test.ts => Component.test.js} (98%) rename unitTests/components/{ComponentV1.test.ts => ComponentV1.test.js} (98%) rename unitTests/components/{EntryHandler.test.ts => EntryHandler.test.js} (97%) rename unitTests/components/{OptionsWatcher.test.ts => OptionsWatcher.test.js} (97%) rename unitTests/components/{Scope.test.ts => Scope.test.js} (80%) rename unitTests/components/{componentLoader.test.ts => componentLoader.test.js} (85%) rename unitTests/components/{deriveCommonPatternBase.test.ts => deriveCommonPatternBase.test.js} (75%) rename unitTests/components/{deriveGlobOption.test.ts => deriveGlobOption.test.js} (92%) rename unitTests/components/{requestRestart.test.ts => requestRestart.test.js} (53%) rename unitTests/components/{resolveBaseURLPath.test.ts => resolveBaseURLPath.test.js} (87%) rename unitTests/components/status/{ComponentStatus.test.ts => ComponentStatus.test.js} (75%) rename unitTests/components/status/{ComponentStatusRegistry.test.ts => ComponentStatusRegistry.test.js} (86%) rename unitTests/components/status/{api.test.ts => api.test.js} (74%) rename unitTests/components/status/{crossThread.test.ts => crossThread.test.js} (87%) rename unitTests/components/status/{errors.test.ts => errors.test.js} (77%) rename unitTests/components/status/{index.test.ts => index.test.js} (85%) rename unitTests/components/status/{registry.test.ts => registry.test.js} (67%) create mode 100644 unitTests/components/waitFor.js delete mode 100644 unitTests/components/waitFor.ts create mode 100644 unitTests/testUtils.js delete mode 100644 unitTests/tsconfig.json diff --git a/package.json b/package.json index 143b453cd..07c893074 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "format:write": "prettier --write .", "test:integration:api-tests": "node --test integrationTests/apiTests/tests/testSuite.mjs", "test:integration": "node integrationTests/utils/scripts/run.ts", - "test:unit": "TSX_TSCONFIG_PATH=./unitTests/tsconfig.json npx mocha --config unitTests/.mocharc.json", + "test:unit": "mocha --config unitTests/.mocharc.json", "test:unit:all": "npm run test:unit unitTests" }, "imports": { diff --git a/unitTests/.mocharc.json b/unitTests/.mocharc.json index 6b7877684..cfc2b7a0a 100644 --- a/unitTests/.mocharc.json +++ b/unitTests/.mocharc.json @@ -1,9 +1,7 @@ { - "$schema": "https://json.schemastore.org/mocharc.json", - "require": "tsx", "timeout": 0, "ui": "bdd", - "extension": ["js", "mjs", "ts"], + "extension": ["js", "mjs"], "recursive": true, "exit": true } diff --git a/unitTests/components/Component.test.ts b/unitTests/components/Component.test.js similarity index 98% rename from unitTests/components/Component.test.ts rename to unitTests/components/Component.test.js index 5b306ba6f..2b16c58f3 100644 --- a/unitTests/components/Component.test.ts +++ b/unitTests/components/Component.test.js @@ -1,6 +1,6 @@ -import { describe, it } from 'mocha'; -import { Component, ComponentInvalidPatternError } from '@/components/Component'; -import assert from 'node:assert/strict'; +const { describe, it } = require('mocha'); +const { Component, ComponentInvalidPatternError } = require('#src/components/Component'); +const assert = require('node:assert/strict'); describe('Component', () => { const name = 'test-component'; diff --git a/unitTests/components/ComponentV1.test.ts b/unitTests/components/ComponentV1.test.js similarity index 98% rename from unitTests/components/ComponentV1.test.ts rename to unitTests/components/ComponentV1.test.js index a8f2982f4..093c44c17 100644 --- a/unitTests/components/ComponentV1.test.ts +++ b/unitTests/components/ComponentV1.test.js @@ -1,8 +1,6 @@ -/* eslint-disable @typescript-eslint/no-require-imports, sonarjs/void-use, sonarjs/assertions-in-tests, sonarjs/no-nested-functions */ -// Not sure why sonar cannot pick up the assertions within the tests. I think its because they are `async`. - -import { tmpdir } from 'node:os'; -import { +const { describe, it, beforeEach, afterEach, after } = require('mocha'); +const { tmpdir } = require('node:os'); +const { processResourceExtensionComponent, ComponentV1, InvalidFilesOptionError, @@ -13,13 +11,13 @@ import { InvalidRootOptionError, InvalidPathOptionError, InvalidURLPathOptionError, -} from '@/components/ComponentV1'; -import { Resources } from '@/resources/Resources'; -import assert from 'node:assert/strict'; -import { join } from 'node:path'; -import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs'; -import { fake, restore, replace } from 'sinon'; -import fg from 'fast-glob'; +} = require('#src/components/ComponentV1'); +const { Resources } = require('#src/resources/Resources'); +const assert = require('node:assert/strict'); +const { join } = require('node:path'); +const { mkdtempSync, writeFileSync, mkdirSync, rmSync } = require('node:fs'); +const { fake, restore, replace } = require('sinon'); +const fg = require('fast-glob'); const TEMP_DIR_PATH = join(tmpdir(), 'harper.unit-test.component-v1-'); @@ -45,7 +43,7 @@ function createTempFixture(fixture) { describe('ComponentV1', () => { const componentName = 'test-component'; - const harperLogger = require('@/utility/logging/harper_logger'); + const harperLogger = require('#js/utility/logging/harper_logger'); beforeEach(() => { replace(harperLogger, 'warn', fake()); diff --git a/unitTests/components/EntryHandler.test.ts b/unitTests/components/EntryHandler.test.js similarity index 97% rename from unitTests/components/EntryHandler.test.ts rename to unitTests/components/EntryHandler.test.js index 6f8a75d5e..7ed51b301 100644 --- a/unitTests/components/EntryHandler.test.ts +++ b/unitTests/components/EntryHandler.test.js @@ -1,12 +1,13 @@ -import { EntryHandler } from '@/components/EntryHandler'; -import { EventEmitter, once } from 'node:events'; -import assert from 'node:assert/strict'; -import { join, basename } from 'node:path'; -import { tmpdir } from 'node:os'; -import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; -import { writeFile, mkdir } from 'node:fs/promises'; -import { spy } from 'sinon'; -import { waitFor } from './waitFor'; +const { describe, it, beforeEach, afterEach } = require('mocha'); +const { EntryHandler } = require('#src/components/EntryHandler'); +const { EventEmitter, once } = require('node:events'); +const assert = require('node:assert/strict'); +const { join, basename } = require('node:path'); +const { tmpdir } = require('node:os'); +const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = require('node:fs'); +const { writeFile, mkdir } = require('node:fs/promises'); +const { spy } = require('sinon'); +const { waitFor } = require('./waitFor.js'); function generateFixture(dirPath, fixture) { mkdirSync(dirPath, { recursive: true }); diff --git a/unitTests/components/OptionsWatcher.test.ts b/unitTests/components/OptionsWatcher.test.js similarity index 97% rename from unitTests/components/OptionsWatcher.test.ts rename to unitTests/components/OptionsWatcher.test.js index 2f4330300..f02e7062e 100644 --- a/unitTests/components/OptionsWatcher.test.ts +++ b/unitTests/components/OptionsWatcher.test.js @@ -1,14 +1,14 @@ -/* eslint-disable sonarjs/no-nested-functions */ -import { OptionsWatcher } from '@/components/OptionsWatcher'; -import { EventEmitter, once } from 'node:events'; -import assert from 'node:assert/strict'; -import { join } from 'node:path'; -import { tmpdir } from 'node:os'; -import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; -import { writeFile, rm } from 'node:fs/promises'; -import { stringify } from 'yaml'; -import { spy } from 'sinon'; -import { DEFAULT_CONFIG } from '@/components/DEFAULT_CONFIG'; +const { describe, it, beforeEach, afterEach } = require('mocha'); +const { OptionsWatcher } = require('#src/components/OptionsWatcher'); +const { EventEmitter, once } = require('node:events'); +const assert = require('node:assert/strict'); +const { join } = require('node:path'); +const { tmpdir } = require('node:os'); +const { mkdtempSync, writeFileSync, rmSync } = require('node:fs'); +const { writeFile, rm } = require('node:fs/promises'); +const { stringify } = require('yaml'); +const { spy } = require('sinon'); +const { DEFAULT_CONFIG } = require('#src/components/DEFAULT_CONFIG'); /** * This function asserts that an event is emitted. diff --git a/unitTests/components/Scope.test.ts b/unitTests/components/Scope.test.js similarity index 80% rename from unitTests/components/Scope.test.ts rename to unitTests/components/Scope.test.js index 38e7dedc0..c66c01dd4 100644 --- a/unitTests/components/Scope.test.ts +++ b/unitTests/components/Scope.test.js @@ -1,19 +1,24 @@ -import { Scope, MissingDefaultFilesOptionError } from '@/components/Scope'; -import { EventEmitter } from 'node:events'; -import assert from 'node:assert/strict'; -import { join, basename } from 'node:path'; -import { tmpdir } from 'node:os'; -import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; -import { stringify } from 'yaml'; -import { spy } from 'sinon'; -import { OptionsWatcher } from '@/components/OptionsWatcher'; -import { Resources } from '@/resources/Resources'; -import { EntryHandler } from '@/components/EntryHandler'; -import { restartNeeded, resetRestartNeeded } from '@/components/requestRestart'; -import { writeFile } from 'node:fs/promises'; -import { waitFor } from './waitFor'; +const { describe, it, before, beforeEach, after, afterEach } = require('mocha'); +const { Scope, MissingDefaultFilesOptionError } = require('#src/components/Scope'); +const { EventEmitter } = require('node:events'); +const assert = require('node:assert/strict'); +const { join, basename } = require('node:path'); +const { tmpdir } = require('node:os'); +const { mkdtempSync, writeFileSync, rmSync } = require('node:fs'); +const { stringify } = require('yaml'); +const { spy } = require('sinon'); +const { OptionsWatcher } = require('#src/components/OptionsWatcher'); +const { Resources } = require('#src/resources/Resources'); +const { EntryHandler } = require('#src/components/EntryHandler'); +const { restartNeeded, resetRestartNeeded } = require('#src/components/requestRestart'); +const { writeFile } = require('node:fs/promises'); +const { waitFor } = require('./waitFor.js'); +const { createTestSandbox, cleanupTestSandbox } = require('../testUtils.js'); describe('Scope', () => { + before(createTestSandbox); + after(cleanupTestSandbox); + beforeEach(() => { this.resources = new Resources(); this.server = {}; @@ -143,10 +148,7 @@ describe('Scope', () => { await scope.ready; - const entryHandler = scope.handleEntry(); - - // Wait for initial load to complete - the default behavior will trigger restart - await entryHandler.ready; + await scope.handleEntry().ready; assert.equal(restartNeeded(), true, 'requestRestart was called'); @@ -160,7 +162,7 @@ describe('Scope', () => { await scope.ready; - scope.handleEntry(() => {}); + await scope.handleEntry(() => {}).ready; assert.equal(restartNeeded(), false, 'requestRestart was not called'); @@ -217,9 +219,6 @@ describe('Scope', () => { const customEntryHandlerPathOnlyArg = scope.handleEntry('.'); assert.ok(customEntryHandlerPathOnlyArg instanceof EntryHandler, 'Custom entry handler should be created'); - // Reset restart flag - the first handler without a function triggers restart when it encounters files - resetRestartNeeded(); - const customEntryHandlerPathAndFunctionArgs = scope.handleEntry('.', () => {}); assert.ok(customEntryHandlerPathAndFunctionArgs instanceof EntryHandler, 'Custom entry handler should be created'); @@ -236,34 +235,4 @@ describe('Scope', () => { assert.equal(entryHandleCloseSpy1.callCount, 1, 'close event for custom entry handler should be emitted once'); assert.equal(entryHandleCloseSpy2.callCount, 1, 'close event for custom entry handler should be emitted once'); }); - - it('should support synchronous handleEntry with event-based initial load tracking', async () => { - writeFileSync(this.configFilePath, stringify({ [this.name]: { files: 'test.js' } })); - - const scope = new Scope(this.name, this.directory, this.configFilePath, this.resources, this.server); - - await scope.ready; - - const handleEntrySpy = spy(); - - // Call handleEntry - returns EntryHandler immediately - const entryHandler = scope.handleEntry(handleEntrySpy); - - // Should return an EntryHandler immediately (not a Promise) - assert.ok(entryHandler instanceof EntryHandler, 'handleEntry should return EntryHandler synchronously'); - - // Can listen for the ready event if needed - const readySpy = spy(); - entryHandler.on('ready', readySpy); - - // Wait for initial load to complete - await entryHandler.ready; - assert.ok(readySpy.calledOnce, 'ready event should be emitted once'); - - // Handler should be called for initial files - await waitFor(() => handleEntrySpy.callCount > 0); - assert.ok(handleEntrySpy.callCount > 0, 'Entry handler should be called'); - - scope.close(); - }); }); diff --git a/unitTests/components/componentLoader.test.ts b/unitTests/components/componentLoader.test.js similarity index 85% rename from unitTests/components/componentLoader.test.ts rename to unitTests/components/componentLoader.test.js index 05a9551d6..5ae7f04da 100644 --- a/unitTests/components/componentLoader.test.ts +++ b/unitTests/components/componentLoader.test.js @@ -1,22 +1,25 @@ -import { describe, it, before, beforeEach, after } from 'mocha'; -import assert from 'node:assert/strict'; -import sinon from 'sinon'; -import path from 'path'; -import { tmpdir } from 'os'; -import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from 'fs'; - -describe('ComponentLoader Status Integration', function () { +const { describe, it, before, after, beforeEach } = require('mocha'); +const assert = require('node:assert/strict'); +const sinon = require('sinon'); +const path = require('path'); +const { tmpdir } = require('os'); +const { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } = require('fs'); +const { createTestSandbox, cleanupTestSandbox } = require('../testUtils.js'); + +describe('ComponentLoader Status Integration', () => { let componentStatusRegistry; let tempDir; let componentLoader; let lifecycle; before(() => { + createTestSandbox(); + // Create a temporary directory for test components tempDir = mkdtempSync(path.join(tmpdir(), 'harper-test-components-')); // Mock environment to use our temp directory - const env = require('@/utility/environment/environmentManager'); + const env = require('#js/utility/environment/environmentManager'); sinon.stub(env, 'get').callsFake((key) => { if (key === 'COMPONENTSROOT') { return tempDir; @@ -30,7 +33,7 @@ describe('ComponentLoader Status Integration', function () { }); // Get both the lifecycle and internal objects - const statusModule = require('@/components/status'); + const statusModule = require('#src/components/status/index'); const { internal } = statusModule; lifecycle = statusModule.lifecycle; componentStatusRegistry = internal.componentStatusRegistry; @@ -48,17 +51,17 @@ describe('ComponentLoader Status Integration', function () { sinon.spy(componentStatusRegistry, 'getStatus'); // Mock getConfigObj to avoid loading real config for root components - const configUtils = require('@/config/configUtils'); + const configUtils = require('#js/config/configUtils'); sinon.stub(configUtils, 'getConfigObj').returns({}); // Clear the componentLoader from require cache to ensure it gets our spied lifecycle - delete require.cache[require.resolve('@/components/componentLoader')]; + delete require.cache[require.resolve('#src/components/componentLoader')]; // Load componentLoader after setting up spies - componentLoader = require('@/components/componentLoader'); + componentLoader = require('#src/components/componentLoader'); }); - after(function () { + after(async () => { // Restore all spies sinon.restore(); @@ -69,9 +72,11 @@ describe('ComponentLoader Status Integration', function () { // Clear the component status registry componentStatusRegistry.reset(); + + await cleanupTestSandbox(); }); - beforeEach(function () { + beforeEach(() => { // Reset spy history before each test lifecycle.loading.resetHistory(); lifecycle.loaded.resetHistory(); @@ -86,8 +91,8 @@ describe('ComponentLoader Status Integration', function () { componentStatusRegistry.reset(); }); - describe('Basic component status tracking', function () { - it('should initialize loading status for non-root components', async function () { + describe('Basic component status tracking', () => { + it('should initialize loading status for non-root components', async () => { // Create a test component directory const componentDirName = 'test-component'; const componentDir = path.join(tempDir, componentDirName); @@ -121,7 +126,7 @@ describe('ComponentLoader Status Integration', function () { ); }); - it('should track loading for components with trusted loaders', async function () { + it('should track loading for components with trusted loaders', async () => { // Create a component using a trusted loader const componentDirName = 'trusted-component'; const componentDir = path.join(tempDir, componentDirName); @@ -154,7 +159,7 @@ describe('ComponentLoader Status Integration', function () { assert.match(loadedCalls[0].args[1], /loaded successfully/); }); - it('should mark component as failed when it loads no functionality', async function () { + it('should mark component as failed when it loads no functionality', async () => { // Create a component directory without config // This will use DEFAULT_CONFIG but won't actually load anything const componentDirName = 'empty-component'; @@ -188,8 +193,8 @@ describe('ComponentLoader Status Integration', function () { }); }); - describe('Component status verification', function () { - it('should properly set status in registry after successful load', async function () { + describe('Component status verification', () => { + it('should properly set status in registry after successful load', async () => { // Create a component const componentDirName = 'verify-status'; const componentDir = path.join(tempDir, componentDirName); @@ -213,19 +218,19 @@ describe('ComponentLoader Status Integration', function () { assert.ok(status.message, 'Should have a status message'); }); - it('should handle component loading errors gracefully', async function () { + it('should handle component loading errors gracefully', async () => { // Stub the dataLoader module's handleApplication method to throw an error - const dataLoaderModule = require('@/resources/dataLoader'); - const originalhandleApplication = dataLoaderModule.handleApplication; - sinon.stub(dataLoaderModule, 'handleApplication').throws(new Error('DataLoader failed to initialize')); + // const dataLoaderModule = require('#src/resources/dataLoader'); + // const originalhandleApplication = dataLoaderModule.handleApplication; + // sinon.stub(dataLoaderModule, 'handleApplication').throws(new Error('DataLoader failed to initialize')); // Create a component that uses dataLoader const componentDirName = 'error-component'; const componentDir = path.join(tempDir, componentDirName); mkdirSync(componentDir); - // Create config that uses dataLoader - writeFileSync(path.join(componentDir, 'harperdb-config.yaml'), 'dataLoader:\n path: "data"'); + // Create a bad dataLoader config + writeFileSync(path.join(componentDir, 'harperdb-config.yaml'), 'dataLoader:\n nope: \ninvalid'); // Create mock resources const mockResources = { @@ -248,7 +253,7 @@ describe('ComponentLoader Status Integration', function () { assert.ok(componentFailure.args[1] instanceof Error, 'Should have passed an Error object'); assert.match( String(componentFailure.args[1]), - /DataLoader failed to initialize/, + /YAMLParseError: Could not load component 'dataLoader' for application 'error-component'/, 'Error should contain our error message' ); @@ -260,7 +265,7 @@ describe('ComponentLoader Status Integration', function () { assert.ok(errorCall, 'Should have created an ErrorResource'); // Restore the original handleApplication method - dataLoaderModule.handleApplication = originalhandleApplication; + // dataLoaderModule.handleApplication = originalhandleApplication; }); }); }); diff --git a/unitTests/components/deriveCommonPatternBase.test.ts b/unitTests/components/deriveCommonPatternBase.test.js similarity index 75% rename from unitTests/components/deriveCommonPatternBase.test.ts rename to unitTests/components/deriveCommonPatternBase.test.js index 5f2e4003f..cdb0e3d7b 100644 --- a/unitTests/components/deriveCommonPatternBase.test.ts +++ b/unitTests/components/deriveCommonPatternBase.test.js @@ -1,5 +1,6 @@ -import { deriveCommonPatternBase } from '@/components/deriveCommonPatternBase'; -import assert from 'node:assert/strict'; +const { describe, it } = require('mocha'); +const { deriveCommonPatternBase } = require('#src/components/deriveCommonPatternBase'); +const assert = require('node:assert/strict'); describe('deriveCommonPatternBase', () => { [ diff --git a/unitTests/components/deriveGlobOption.test.ts b/unitTests/components/deriveGlobOption.test.js similarity index 92% rename from unitTests/components/deriveGlobOption.test.ts rename to unitTests/components/deriveGlobOption.test.js index 1abc9838d..23179c516 100644 --- a/unitTests/components/deriveGlobOption.test.ts +++ b/unitTests/components/deriveGlobOption.test.js @@ -1,5 +1,6 @@ -import assert from 'node:assert/strict'; -import { deriveGlobOptions } from '@/components/deriveGlobOptions'; +const { describe, it } = require('mocha'); +const assert = require('node:assert/strict'); +const { deriveGlobOptions } = require('#src/components/deriveGlobOptions'); // components/deriveGlobOptions.test.ts diff --git a/unitTests/components/requestRestart.test.ts b/unitTests/components/requestRestart.test.js similarity index 53% rename from unitTests/components/requestRestart.test.ts rename to unitTests/components/requestRestart.test.js index 64854ab0b..a0ecdae86 100644 --- a/unitTests/components/requestRestart.test.ts +++ b/unitTests/components/requestRestart.test.js @@ -1,5 +1,6 @@ -import { requestRestart, restartNeeded } from '@/components/requestRestart'; -import assert from 'node:assert/strict'; +const { describe, it } = require('mocha'); +const { requestRestart, restartNeeded } = require('#src/components/requestRestart'); +const assert = require('node:assert/strict'); describe('requestRestart', () => { it('should update the shared buffer', () => { diff --git a/unitTests/components/resolveBaseURLPath.test.ts b/unitTests/components/resolveBaseURLPath.test.js similarity index 87% rename from unitTests/components/resolveBaseURLPath.test.ts rename to unitTests/components/resolveBaseURLPath.test.js index 28b8360dc..4af848277 100644 --- a/unitTests/components/resolveBaseURLPath.test.ts +++ b/unitTests/components/resolveBaseURLPath.test.js @@ -1,5 +1,6 @@ -import { resolveBaseURLPath, InvalidBaseURLPathError } from '@/components/resolveBaseURLPath'; -import assert from 'node:assert/strict'; +const { describe, it } = require('mocha'); +const { resolveBaseURLPath, InvalidBaseURLPathError } = require('#src/components/resolveBaseURLPath'); +const assert = require('node:assert/strict'); describe('resolveBaseURLPath', () => { const componentName = 'test-component'; diff --git a/unitTests/components/status/ComponentStatus.test.ts b/unitTests/components/status/ComponentStatus.test.js similarity index 75% rename from unitTests/components/status/ComponentStatus.test.ts rename to unitTests/components/status/ComponentStatus.test.js index a640f3ab5..907016c75 100644 --- a/unitTests/components/status/ComponentStatus.test.ts +++ b/unitTests/components/status/ComponentStatus.test.js @@ -1,23 +1,23 @@ -import assert from 'node:assert/strict'; -import sinon from 'sinon'; -import { ComponentStatus } from '@/components/status/ComponentStatus'; -import { COMPONENT_STATUS_LEVELS } from '@/components/status/types'; -import { describe, it, beforeEach, afterEach } from 'mocha'; +const { describe, it, beforeEach, afterEach } = require('mocha'); +const assert = require('node:assert/strict'); +const sinon = require('sinon'); +const { ComponentStatus } = require('#src/components/status/ComponentStatus'); +const { COMPONENT_STATUS_LEVELS } = require('#src/components/status/types'); -describe('ComponentStatus', function () { +describe('ComponentStatus', () => { let clock; - beforeEach(function () { + beforeEach(() => { // Use fake timers to control Date objects clock = sinon.useFakeTimers(); }); - afterEach(function () { + afterEach(() => { clock.restore(); }); - describe('constructor', function () { - it('should create a ComponentStatus with all parameters', function () { + describe('constructor', () => { + it('should create a ComponentStatus with all parameters', () => { const error = new Error('Test error'); const status = new ComponentStatus('error', 'Component failed', error); @@ -28,7 +28,7 @@ describe('ComponentStatus', function () { assert.equal(status.lastChecked.getTime(), 0); // fake timer starts at 0 }); - it('should create a ComponentStatus without optional parameters', function () { + it('should create a ComponentStatus without optional parameters', () => { const status = new ComponentStatus('healthy'); assert.equal(status.status, 'healthy'); @@ -37,15 +37,15 @@ describe('ComponentStatus', function () { assert.ok(status.lastChecked instanceof Date); }); - it('should accept string as error', function () { + it('should accept string as error', () => { const status = new ComponentStatus('error', 'Component failed', 'String error'); assert.equal(status.error, 'String error'); }); }); - describe('updateStatus', function () { - it('should update status and message', function () { + describe('updateStatus', () => { + it('should update status and message', () => { const status = new ComponentStatus('loading', 'Starting up'); // Advance time @@ -58,7 +58,7 @@ describe('ComponentStatus', function () { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should clear error when status is not ERROR', function () { + it('should clear error when status is not ERROR', () => { const error = new Error('Test error'); const status = new ComponentStatus('error', 'Failed', error); @@ -67,7 +67,7 @@ describe('ComponentStatus', function () { assert.equal(status.error, undefined); }); - it('should keep error when status remains ERROR', function () { + it('should keep error when status remains ERROR', () => { const error = new Error('Test error'); const status = new ComponentStatus('error', 'Failed', error); @@ -76,7 +76,7 @@ describe('ComponentStatus', function () { assert.equal(status.error, error); }); - it('should update without message', function () { + it('should update without message', () => { const status = new ComponentStatus('loading'); status.updateStatus('healthy'); @@ -86,8 +86,8 @@ describe('ComponentStatus', function () { }); }); - describe('markHealthy', function () { - it('should set status to healthy with custom message', function () { + describe('markHealthy', () => { + it('should set status to healthy with custom message', () => { const status = new ComponentStatus('loading'); clock.tick(1000); @@ -98,7 +98,7 @@ describe('ComponentStatus', function () { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should set status to healthy with default message', function () { + it('should set status to healthy with default message', () => { const status = new ComponentStatus('error'); status.markHealthy(); @@ -107,7 +107,7 @@ describe('ComponentStatus', function () { assert.equal(status.message, 'Component is healthy'); }); - it('should clear error when marking healthy', function () { + it('should clear error when marking healthy', () => { const status = new ComponentStatus('error', 'Failed', new Error('Test')); status.markHealthy(); @@ -116,8 +116,8 @@ describe('ComponentStatus', function () { }); }); - describe('markError', function () { - it('should set status to error with Error object', function () { + describe('markError', () => { + it('should set status to error with Error object', () => { const status = new ComponentStatus('healthy'); const error = new Error('Something went wrong'); @@ -130,7 +130,7 @@ describe('ComponentStatus', function () { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should set status to error with string error', function () { + it('should set status to error with string error', () => { const status = new ComponentStatus('healthy'); status.markError('String error message'); @@ -140,7 +140,7 @@ describe('ComponentStatus', function () { assert.equal(status.message, 'String error message'); }); - it('should use error message when no custom message provided', function () { + it('should use error message when no custom message provided', () => { const status = new ComponentStatus('healthy'); const error = new Error('Error from exception'); @@ -150,8 +150,8 @@ describe('ComponentStatus', function () { }); }); - describe('markWarning', function () { - it('should set status to warning with message', function () { + describe('markWarning', () => { + it('should set status to warning with message', () => { const status = new ComponentStatus('healthy'); clock.tick(1000); @@ -162,7 +162,7 @@ describe('ComponentStatus', function () { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should clear error when marking warning', function () { + it('should clear error when marking warning', () => { const status = new ComponentStatus('error', 'Failed', new Error('Test')); status.markWarning('Recovered with warnings'); @@ -171,8 +171,8 @@ describe('ComponentStatus', function () { }); }); - describe('markLoading', function () { - it('should set status to loading with custom message', function () { + describe('markLoading', () => { + it('should set status to loading with custom message', () => { const status = new ComponentStatus('unknown'); clock.tick(1000); @@ -183,7 +183,7 @@ describe('ComponentStatus', function () { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should set status to loading with default message', function () { + it('should set status to loading with default message', () => { const status = new ComponentStatus('unknown'); status.markLoading(); @@ -193,8 +193,8 @@ describe('ComponentStatus', function () { }); }); - describe('status check methods', function () { - it('should correctly identify healthy status', function () { + describe('status check methods', () => { + it('should correctly identify healthy status', () => { const healthyStatus = new ComponentStatus('healthy'); const errorStatus = new ComponentStatus('error'); @@ -202,7 +202,7 @@ describe('ComponentStatus', function () { assert.equal(errorStatus.isHealthy(), false); }); - it('should correctly identify error status', function () { + it('should correctly identify error status', () => { const errorStatus = new ComponentStatus('error'); const healthyStatus = new ComponentStatus('healthy'); @@ -210,7 +210,7 @@ describe('ComponentStatus', function () { assert.equal(healthyStatus.hasError(), false); }); - it('should correctly identify loading status', function () { + it('should correctly identify loading status', () => { const loadingStatus = new ComponentStatus('loading'); const healthyStatus = new ComponentStatus('healthy'); @@ -218,7 +218,7 @@ describe('ComponentStatus', function () { assert.equal(healthyStatus.isLoading(), false); }); - it('should correctly identify warning status', function () { + it('should correctly identify warning status', () => { const warningStatus = new ComponentStatus('warning'); const healthyStatus = new ComponentStatus('healthy'); @@ -227,20 +227,20 @@ describe('ComponentStatus', function () { }); }); - describe('getSummary', function () { - it('should return summary with message', function () { + describe('getSummary', () => { + it('should return summary with message', () => { const status = new ComponentStatus('error', 'Database connection failed'); assert.equal(status.getSummary(), 'ERROR: Database connection failed'); }); - it('should return summary without message', function () { + it('should return summary without message', () => { const status = new ComponentStatus('healthy'); assert.equal(status.getSummary(), 'HEALTHY'); }); - it('should handle all status levels', function () { + it('should handle all status levels', () => { const statusLevels = ['healthy', 'warning', 'error', 'loading', 'unknown']; for (const level of statusLevels) { @@ -250,8 +250,8 @@ describe('ComponentStatus', function () { }); }); - describe('status transitions', function () { - it('should transition through multiple states correctly', function () { + describe('status transitions', () => { + it('should transition through multiple states correctly', () => { const status = new ComponentStatus('unknown'); // Unknown -> Loading diff --git a/unitTests/components/status/ComponentStatusRegistry.test.ts b/unitTests/components/status/ComponentStatusRegistry.test.js similarity index 86% rename from unitTests/components/status/ComponentStatusRegistry.test.ts rename to unitTests/components/status/ComponentStatusRegistry.test.js index c0c78e964..fc4509fe0 100644 --- a/unitTests/components/status/ComponentStatusRegistry.test.ts +++ b/unitTests/components/status/ComponentStatusRegistry.test.js @@ -1,40 +1,37 @@ -import assert from 'node:assert/strict'; -import sinon from 'sinon'; -import { ComponentStatusRegistry } from '@/components/status/ComponentStatusRegistry'; -import { ComponentStatus } from '@/components/status/ComponentStatus'; -import { COMPONENT_STATUS_LEVELS } from '@/components/status/types'; -import { StatusAggregator } from '@/components/status/crossThread'; - -describe('ComponentStatusRegistry', function () { +const { describe, it, beforeEach, afterEach, after } = require('mocha'); +const assert = require('node:assert/strict'); +const sinon = require('sinon'); +const { ComponentStatusRegistry } = require('#src/components/status/ComponentStatusRegistry'); +const { ComponentStatus } = require('#src/components/status/ComponentStatus'); +const { COMPONENT_STATUS_LEVELS } = require('#src/components/status/types'); +const { StatusAggregator } = require('#src/components/status/crossThread'); +const manageThreadsModule = require('#js/server/threads/manageThreads'); + +describe('ComponentStatusRegistry', () => { let registry; let clock; - beforeEach(function () { + beforeEach(() => { registry = new ComponentStatusRegistry(); clock = sinon.useFakeTimers(); - // Stub ITC functions - const itcModule = require('@/server/threads/itc'); - const manageThreadsModule = require('@/server/threads/manageThreads'); - sinon.stub(itcModule, 'sendItcEvent').resolves(); sinon.stub(manageThreadsModule, 'onMessageByType'); sinon.stub(manageThreadsModule, 'getWorkerIndex').returns(0); }); - afterEach(function () { + afterEach(() => { clock.restore(); sinon.restore(); - // Reset the registry to ensure clean state registry.reset(); }); - after(function () { + after(() => { // Clean up any environment variables that might have been set delete process.env.COMPONENT_STATUS_TIMEOUT; }); - describe('reset', function () { - it('should clear all statuses', function () { + describe('reset', () => { + it('should clear all statuses', () => { registry.setStatus('comp1', 'healthy', 'All good'); registry.setStatus('comp2', 'error', 'Failed'); @@ -46,8 +43,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('setStatus', function () { - it('should set component status with all parameters', function () { + describe('setStatus', () => { + it('should set component status with all parameters', () => { const error = new Error('Test error'); registry.setStatus('database', 'error', 'Connection failed', error); @@ -58,7 +55,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(status.error, error); }); - it('should set component status without optional parameters', function () { + it('should set component status without optional parameters', () => { registry.setStatus('cache', 'healthy'); const status = registry.getStatus('cache'); @@ -67,7 +64,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(status.error, undefined); }); - it('should overwrite existing status', function () { + it('should overwrite existing status', () => { registry.setStatus('api', 'loading', 'Starting up'); registry.setStatus('api', 'healthy', 'Ready'); @@ -76,7 +73,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(status.message, 'Ready'); }); - it('should throw error for invalid component name', function () { + it('should throw error for invalid component name', () => { assert.throws(() => registry.setStatus('', 'healthy'), { name: 'ComponentStatusOperationError', message: /Component name must be a non-empty string/, @@ -88,7 +85,7 @@ describe('ComponentStatusRegistry', function () { }); }); - it('should throw error for invalid status level', function () { + it('should throw error for invalid status level', () => { assert.throws(() => registry.setStatus('comp4', 'invalid-status'), { name: 'ComponentStatusOperationError', message: /Invalid status level: invalid-status/, @@ -96,12 +93,12 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('getStatus', function () { - it('should return undefined for non-existent component', function () { + describe('getStatus', () => { + it('should return undefined for non-existent component', () => { assert.equal(registry.getStatus('non-existent'), undefined); }); - it('should return ComponentStatus instance', function () { + it('should return ComponentStatus instance', () => { registry.setStatus('test', 'healthy'); const status = registry.getStatus('test'); @@ -109,14 +106,14 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('getAllStatuses', function () { - it('should return empty map initially', function () { + describe('getAllStatuses', () => { + it('should return empty map initially', () => { const statuses = registry.getAllStatuses(); assert.ok(statuses instanceof Map); assert.equal(statuses.size, 0); }); - it('should return all registered statuses', function () { + it('should return all registered statuses', () => { registry.setStatus('comp1', 'healthy'); registry.setStatus('comp2', 'warning', 'High memory'); registry.setStatus('comp3', 'error', 'Failed'); @@ -129,8 +126,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('reportHealthy', function () { - it('should set status to healthy with message', function () { + describe('reportHealthy', () => { + it('should set status to healthy with message', () => { registry.reportHealthy('service', 'Running smoothly'); const status = registry.getStatus('service'); @@ -138,7 +135,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(status.message, 'Running smoothly'); }); - it('should set status to healthy without message', function () { + it('should set status to healthy without message', () => { registry.reportHealthy('service'); const status = registry.getStatus('service'); @@ -146,8 +143,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('reportError', function () { - it('should set status to error with Error object', function () { + describe('reportError', () => { + it('should set status to error with Error object', () => { const error = new Error('Connection timeout'); registry.reportError('database', error, 'DB connection failed'); @@ -157,7 +154,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(status.error, error); }); - it('should set status to error with string error', function () { + it('should set status to error with string error', () => { registry.reportError('api', 'Invalid configuration'); const status = registry.getStatus('api'); @@ -166,8 +163,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('reportWarning', function () { - it('should set status to warning with message', function () { + describe('reportWarning', () => { + it('should set status to warning with message', () => { registry.reportWarning('cache', 'Cache size approaching limit'); const status = registry.getStatus('cache'); @@ -176,9 +173,9 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('lifecycle management methods', function () { - describe('initializeLoading', function () { - it('should set status to loading with custom message', function () { + describe('lifecycle management methods', () => { + describe('initializeLoading', () => { + it('should set status to loading with custom message', () => { registry.initializeLoading('auth', 'Connecting to auth server'); const status = registry.getStatus('auth'); @@ -186,7 +183,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(status.message, 'Connecting to auth server'); }); - it('should set status to loading with default message', function () { + it('should set status to loading with default message', () => { registry.initializeLoading('auth'); const status = registry.getStatus('auth'); @@ -195,8 +192,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('markLoaded', function () { - it('should set status to healthy with custom message', function () { + describe('markLoaded', () => { + it('should set status to healthy with custom message', () => { registry.markLoaded('storage', 'Storage initialized'); const status = registry.getStatus('storage'); @@ -204,7 +201,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(status.message, 'Storage initialized'); }); - it('should set status to healthy with default message', function () { + it('should set status to healthy with default message', () => { registry.markLoaded('storage'); const status = registry.getStatus('storage'); @@ -213,8 +210,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('markFailed', function () { - it('should set status to error with all parameters', function () { + describe('markFailed', () => { + it('should set status to error with all parameters', () => { const error = new Error('Init failed'); registry.markFailed('logger', error, 'Failed to initialize logger'); @@ -224,7 +221,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(status.error, error); }); - it('should set status to error with string error', function () { + it('should set status to error with string error', () => { registry.markFailed('logger', 'Configuration missing'); const status = registry.getStatus('logger'); @@ -234,8 +231,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('getComponentsByStatus', function () { - beforeEach(function () { + describe('getComponentsByStatus', () => { + beforeEach(() => { registry.setStatus('comp1', 'healthy'); registry.setStatus('comp2', 'error', 'Failed'); registry.setStatus('comp3', 'healthy'); @@ -243,19 +240,19 @@ describe('ComponentStatusRegistry', function () { registry.setStatus('comp5', 'error', 'Timeout'); }); - it('should return components with specific status', function () { + it('should return components with specific status', () => { const healthyComponents = registry.getComponentsByStatus(COMPONENT_STATUS_LEVELS.HEALTHY); assert.equal(healthyComponents.length, 2); assert.equal(healthyComponents[0].name, 'comp1'); assert.equal(healthyComponents[1].name, 'comp3'); }); - it('should return empty array for status with no components', function () { + it('should return empty array for status with no components', () => { const loadingComponents = registry.getComponentsByStatus(COMPONENT_STATUS_LEVELS.LOADING); assert.equal(loadingComponents.length, 0); }); - it('should return correct component objects', function () { + it('should return correct component objects', () => { const errorComponents = registry.getComponentsByStatus(COMPONENT_STATUS_LEVELS.ERROR); assert.equal(errorComponents.length, 2); @@ -269,8 +266,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('getStatusSummary', function () { - it('should return initial summary with zero counts', function () { + describe('getStatusSummary', () => { + it('should return initial summary with zero counts', () => { const summary = registry.getStatusSummary(); assert.equal(summary[COMPONENT_STATUS_LEVELS.HEALTHY], 0); @@ -280,7 +277,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(summary[COMPONENT_STATUS_LEVELS.UNKNOWN], 0); }); - it('should count components by status', function () { + it('should count components by status', () => { registry.setStatus('comp1', 'healthy'); registry.setStatus('comp2', 'healthy'); registry.setStatus('comp3', 'error'); @@ -299,8 +296,8 @@ describe('ComponentStatusRegistry', function () { }); // Test aggregate functionality through getAggregatedFromAllThreads - describe('aggregation functionality (via getAggregatedFromAllThreads)', function () { - it('should aggregate single component from multiple threads', function () { + describe('aggregation functionality (via getAggregatedFromAllThreads)', () => { + it('should aggregate single component from multiple threads', () => { const allStatuses = new Map([ [ 'myComponent@main', @@ -341,7 +338,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(aggStatus.abnormalities, undefined); // All healthy, no abnormalities }); - it('should detect abnormalities when statuses differ', function () { + it('should detect abnormalities when statuses differ', () => { const allStatuses = new Map([ [ 'database@worker-0', @@ -384,7 +381,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(worker0Abnormality.workerIndex, -1); // No workerIndex in input }); - it('should prioritize non-healthy messages', function () { + it('should prioritize non-healthy messages', () => { const allStatuses = new Map([ [ 'api@worker-0', @@ -419,7 +416,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(aggStatus.latestMessage, 'High latency detected'); // Non-healthy message preferred }); - it('should handle status priority correctly', function () { + it('should handle status priority correctly', () => { const testCases = [ { statuses: ['healthy', 'healthy', 'healthy'], expected: 'healthy' }, { statuses: ['healthy', 'unknown', 'healthy'], expected: 'unknown' }, @@ -443,7 +440,7 @@ describe('ComponentStatusRegistry', function () { } }); - it('should handle worker index in status data', function () { + it('should handle worker index in status data', () => { const allStatuses = new Map([ [ 'service@worker-1', @@ -473,7 +470,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(abnormality.workerIndex, 2); }); - it('should handle main thread correctly', function () { + it('should handle main thread correctly', () => { const allStatuses = new Map([ [ 'logger@main', @@ -501,10 +498,10 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('static getAggregatedFromAllThreads method', function () { - it('should collect and aggregate statuses', async function () { + describe('static getAggregatedFromAllThreads method', () => { + it('should collect and aggregate statuses', async () => { // Mock the crossThreadCollector to avoid actual ITC communication - const { crossThreadCollector } = require('@/components/status/crossThread'); + const { crossThreadCollector } = require('#src/components/status/crossThread'); const originalCollect = crossThreadCollector.collect; // Create a fresh registry for this test @@ -543,9 +540,9 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('getAggregatedStatusFor method', function () { - describe('basic aggregation scenarios', function () { - it('should return status for exact component match only', async function () { + describe('getAggregatedStatusFor method', () => { + describe('basic aggregation scenarios', () => { + it('should return status for exact component match only', async () => { const consolidatedStatuses = new Map([ [ 'application-template', @@ -566,7 +563,7 @@ describe('ComponentStatusRegistry', function () { assert.deepEqual(result.lastChecked, { workers: { 0: 1000 } }); }); - it('should return aggregated status for sub-components only', async function () { + it('should return aggregated status for sub-components only', async () => { const consolidatedStatuses = new Map([ [ 'application-template.rest', @@ -596,7 +593,7 @@ describe('ComponentStatusRegistry', function () { assert.deepEqual(result.lastChecked, { workers: { 0: 1000 } }); }); - it('should combine exact match with sub-components', async function () { + it('should combine exact match with sub-components', async () => { const consolidatedStatuses = new Map([ [ 'application-template', @@ -626,7 +623,7 @@ describe('ComponentStatusRegistry', function () { assert.deepEqual(result.lastChecked, { workers: { 0: 1000 } }); }); - it('should return unknown status when component not found', async function () { + it('should return unknown status when component not found', async () => { const consolidatedStatuses = new Map([ [ 'other-component', @@ -647,8 +644,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('status priority and aggregation logic', function () { - it('should prioritize error over other statuses', async function () { + describe('status priority and aggregation logic', () => { + it('should prioritize error over other statuses', async () => { const consolidatedStatuses = new Map([ [ 'app.component1', @@ -687,7 +684,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(result.details['app.component2'].status, COMPONENT_STATUS_LEVELS.ERROR); }); - it('should prioritize loading over healthy statuses', async function () { + it('should prioritize loading over healthy statuses', async () => { const consolidatedStatuses = new Map([ [ 'service.api', @@ -717,7 +714,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(result.details['service.database'].status, COMPONENT_STATUS_LEVELS.LOADING); }); - it('should return healthy when all components healthy', async function () { + it('should return healthy when all components healthy', async () => { const consolidatedStatuses = new Map([ [ 'webapp.frontend', @@ -746,7 +743,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(result.details, undefined); }); - it('should handle mixed status scenarios correctly', async function () { + it('should handle mixed status scenarios correctly', async () => { const consolidatedStatuses = new Map([ [ 'mixed', @@ -803,8 +800,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('details and message generation', function () { - it('should include details when components have issues', async function () { + describe('details and message generation', () => { + it('should include details when components have issues', async () => { const consolidatedStatuses = new Map([ [ 'app.good', @@ -835,7 +832,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(result.details['app.good'], undefined); // Healthy components not included }); - it('should not include details when all components healthy', async function () { + it('should not include details when all components healthy', async () => { const consolidatedStatuses = new Map([ [ 'service.web', @@ -863,7 +860,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(result.message, 'All components loaded successfully'); }); - it('should generate descriptive messages for problem components', async function () { + it('should generate descriptive messages for problem components', async () => { const consolidatedStatuses = new Map([ [ 'system.auth', @@ -892,7 +889,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(result.message, expectedMessage); }); - it('should format component keys correctly in messages', async function () { + it('should format component keys correctly in messages', async () => { const consolidatedStatuses = new Map([ [ 'my-app.long-component-name', @@ -911,8 +908,8 @@ describe('ComponentStatusRegistry', function () { }); }); - describe('edge cases and error handling', function () { - it('should handle empty consolidated statuses gracefully', async function () { + describe('edge cases and error handling', () => { + it('should handle empty consolidated statuses gracefully', async () => { const consolidatedStatuses = new Map(); const result = await registry.getAggregatedStatusFor('any-component', consolidatedStatuses); @@ -922,7 +919,7 @@ describe('ComponentStatusRegistry', function () { assert.deepEqual(result.lastChecked, { workers: {} }); }); - it('should handle null/undefined consolidated statuses', async function () { + it('should handle null/undefined consolidated statuses', async () => { // Mock the static method to avoid cross-thread communication in tests const originalMethod = ComponentStatusRegistry.getAggregatedFromAllThreads; ComponentStatusRegistry.getAggregatedFromAllThreads = async () => new Map(); @@ -941,7 +938,7 @@ describe('ComponentStatusRegistry', function () { } }); - it('should work with pre-provided consolidated statuses', async function () { + it('should work with pre-provided consolidated statuses', async () => { const consolidatedStatuses = new Map([ [ 'provided.test', @@ -960,7 +957,7 @@ describe('ComponentStatusRegistry', function () { assert.equal(result.message, 'All components loaded successfully'); }); - it('should fetch consolidated statuses when not provided', async function () { + it('should fetch consolidated statuses when not provided', async () => { // This test ensures the method works without pre-provided statuses // It will attempt to fetch from ComponentStatusRegistry.getAggregatedFromAllThreads registry.setStatus('test-fetch', COMPONENT_STATUS_LEVELS.HEALTHY, 'Local component'); @@ -992,7 +989,7 @@ describe('ComponentStatusRegistry', function () { } }); - it('should handle missing latestMessage gracefully', async function () { + it('should handle missing latestMessage gracefully', async () => { const consolidatedStatuses = new Map([ [ 'no-msg.component', diff --git a/unitTests/components/status/api.test.ts b/unitTests/components/status/api.test.js similarity index 74% rename from unitTests/components/status/api.test.ts rename to unitTests/components/status/api.test.js index 093ae229f..995e29642 100644 --- a/unitTests/components/status/api.test.ts +++ b/unitTests/components/status/api.test.js @@ -1,15 +1,12 @@ -import assert from 'node:assert/strict'; -import { statusForComponent, lifecycle, reset, STATUS, internal } from '@/../components/status'; -import { describe, it, afterEach } from 'mocha'; - -describe('Component Status Public API', function () { - afterEach(function () { - // Clean up after each test - reset(); - }); +const { describe, it, afterEach } = require('mocha'); +const assert = require('node:assert/strict'); +const { statusForComponent, lifecycle, reset, STATUS, internal } = require('#src/components/status/index'); + +describe('Component Status Public API', () => { + afterEach(reset); - describe('statusForComponent() API', function () { - it('should provide fluent interface for status reporting', function () { + describe('statusForComponent() API', () => { + it('should provide fluent interface for status reporting', () => { // Test chaining const result = statusForComponent('test-service') .healthy('Service initialized') @@ -25,7 +22,7 @@ describe('Component Status Public API', function () { assert.equal(status.message, 'Connection lost'); }); - it('should report healthy status', function () { + it('should report healthy status', () => { statusForComponent('api').healthy('API server running'); const status = statusForComponent('api').get(); @@ -33,7 +30,7 @@ describe('Component Status Public API', function () { assert.equal(status.message, 'API server running'); }); - it('should report healthy status without message', function () { + it('should report healthy status without message', () => { statusForComponent('cache').healthy(); const status = statusForComponent('cache').get(); @@ -41,7 +38,7 @@ describe('Component Status Public API', function () { assert.equal(status.message, undefined); }); - it('should report warning status', function () { + it('should report warning status', () => { statusForComponent('database').warning('Replication lag detected'); const status = statusForComponent('database').get(); @@ -49,7 +46,7 @@ describe('Component Status Public API', function () { assert.equal(status.message, 'Replication lag detected'); }); - it('should report error status with Error object', function () { + it('should report error status with Error object', () => { const error = new Error('Connection timeout'); statusForComponent('redis').error('Redis connection failed', error); @@ -59,7 +56,7 @@ describe('Component Status Public API', function () { assert.equal(status.error, error); }); - it('should report loading status', function () { + it('should report loading status', () => { statusForComponent('ml-model').loading('Loading model weights'); const status = statusForComponent('ml-model').get(); @@ -67,7 +64,7 @@ describe('Component Status Public API', function () { assert.equal(status.message, 'Loading model weights'); }); - it('should report loading status with default message', function () { + it('should report loading status with default message', () => { statusForComponent('data-processor').loading(); const status = statusForComponent('data-processor').get(); @@ -75,7 +72,7 @@ describe('Component Status Public API', function () { assert.equal(status.message, 'Loading...'); }); - it('should report unknown status', function () { + it('should report unknown status', () => { statusForComponent('mystery').unknown('State unclear'); const status = statusForComponent('mystery').get(); @@ -83,21 +80,21 @@ describe('Component Status Public API', function () { assert.equal(status.message, 'State unclear'); }); - it('should reuse builder instances', function () { + it('should reuse builder instances', () => { const builder1 = statusForComponent('shared'); const builder2 = statusForComponent('shared'); assert.strictEqual(builder1, builder2); }); - it('should return undefined for non-existent component', function () { + it('should return undefined for non-existent component', () => { const status = statusForComponent('non-existent').get(); assert.equal(status, undefined); }); }); - describe('lifecycle API', function () { - it('should handle component loading lifecycle', function () { + describe('lifecycle API', () => { + it('should handle component loading lifecycle', () => { // Loading phase lifecycle.loading('auth-service', 'Initializing authentication'); let status = internal.query.get('auth-service'); @@ -111,7 +108,7 @@ describe('Component Status Public API', function () { assert.equal(status.message, 'Authentication ready'); }); - it('should handle component failure', function () { + it('should handle component failure', () => { lifecycle.loading('payment-gateway'); const error = new Error('Invalid API key'); @@ -124,8 +121,8 @@ describe('Component Status Public API', function () { }); }); - describe('reset API', function () { - it('should clear all component statuses', function () { + describe('reset API', () => { + it('should clear all component statuses', () => { statusForComponent('temp1').healthy(); statusForComponent('temp2').error('Failed'); @@ -139,8 +136,8 @@ describe('Component Status Public API', function () { }); }); - describe('STATUS constants', function () { - it('should expose status level constants', function () { + describe('STATUS constants', () => { + it('should expose status level constants', () => { assert.equal(STATUS.HEALTHY, 'healthy'); assert.equal(STATUS.WARNING, 'warning'); assert.equal(STATUS.ERROR, 'error'); diff --git a/unitTests/components/status/crossThread.test.ts b/unitTests/components/status/crossThread.test.js similarity index 87% rename from unitTests/components/status/crossThread.test.ts rename to unitTests/components/status/crossThread.test.js index 675aff76c..055099fda 100644 --- a/unitTests/components/status/crossThread.test.ts +++ b/unitTests/components/status/crossThread.test.js @@ -1,29 +1,28 @@ -import assert from 'node:assert/strict'; -import sinon from 'sinon'; -import { CrossThreadStatusCollector, StatusAggregator } from '@/components/status/crossThread'; -import { ComponentStatusRegistry } from '@/components/status/ComponentStatusRegistry'; - -describe('CrossThread Module', function () { +const { describe, it, beforeEach, afterEach } = require('mocha'); +const assert = require('node:assert/strict'); +const sinon = require('sinon'); +const { CrossThreadStatusCollector, StatusAggregator } = require('#src/components/status/crossThread'); +const { ComponentStatusRegistry } = require('#src/components/status/ComponentStatusRegistry'); +const itcModule = require('#src/server/threads/itc'); +const manageThreadsModule = require('#js/server/threads/manageThreads'); + +describe('CrossThread Module #skip-typestrip', () => { let sendItcEventStub; let onMessageByTypeStub; let getWorkerIndexStub; - let itcModule; - let manageThreadsModule; - beforeEach(function () { + beforeEach(() => { // Stub ITC functions - itcModule = require('@/server/threads/itc'); - manageThreadsModule = require('@/server/threads/manageThreads'); sendItcEventStub = sinon.stub(itcModule, 'sendItcEvent').resolves(); onMessageByTypeStub = sinon.stub(manageThreadsModule, 'onMessageByType'); getWorkerIndexStub = sinon.stub(manageThreadsModule, 'getWorkerIndex').returns(0); }); - afterEach(function () { + afterEach(() => { sinon.restore(); }); - describe('CrossThreadStatusCollector', function () { + describe('CrossThreadStatusCollector', () => { let collector; let registry; @@ -32,12 +31,12 @@ describe('CrossThread Module', function () { registry = new ComponentStatusRegistry(); }); - afterEach(function () { + afterEach(() => { collector.cleanup(); registry.reset(); }); - it('should collect status from local thread only when no responses', async function () { + it('should collect status from local thread only when no responses', async () => { registry.setStatus('localComp', 'healthy', 'All good'); // Test with main thread (undefined) getWorkerIndexStub.returns(undefined); @@ -58,8 +57,7 @@ describe('CrossThread Module', function () { getWorkerCountStub.restore(); }); - it('should collect status from multiple threads', async function () { - this.timeout(5000); // Allow enough time for async operations + it('should collect status from multiple threads', async () => { registry.setStatus('sharedComp', 'healthy', 'Local is healthy'); // Test with main thread (undefined) getWorkerIndexStub.returns(undefined); @@ -119,9 +117,9 @@ describe('CrossThread Module', function () { // Cleanup getWorkerCountStub.restore(); - }); + }).timeout(5000); - it('should handle ITC send failure', async function () { + it('should handle ITC send failure', async () => { registry.setStatus('fallbackComp', 'error', 'Local error'); // Test with main thread (undefined) getWorkerIndexStub.returns(undefined); @@ -144,8 +142,7 @@ describe('CrossThread Module', function () { getWorkerCountStub.restore(); }); - it('should handle collection timeout', async function () { - this.timeout(5000); + it('should handle collection timeout', async () => { const shortTimeoutCollector = new CrossThreadStatusCollector(50); // Very short timeout registry.setStatus('timeoutComp', 'loading', 'Loading...'); // Test with main thread (undefined) @@ -165,15 +162,14 @@ describe('CrossThread Module', function () { shortTimeoutCollector.cleanup(); getWorkerCountStub.restore(); - }); + }).timeout(5000); - it('should complete early when all threads respond', async function () { - this.timeout(5000); + it('should complete early when all threads respond', async () => { registry.setStatus('fastComp', 'healthy', 'Main thread'); getWorkerIndexStub.returns(0); // Mock getWorkerCount to return 2 (expecting 2 worker responses) - const manageThreadsModule = require('@/server/threads/manageThreads'); + const manageThreadsModule = require('#js/server/threads/manageThreads'); const getWorkerCountStub = sinon.stub(manageThreadsModule, 'getWorkerCount').returns(2); // Track when collection completes @@ -215,9 +211,9 @@ describe('CrossThread Module', function () { // Cleanup the stub getWorkerCountStub.restore(); - }); + }).timeout(5000); - it('should reuse listener across multiple collections', async function () { + it('should reuse listener across multiple collections', async () => { // First collection await collector.collect(registry); assert.equal(onMessageByTypeStub.callCount, 1); @@ -227,7 +223,7 @@ describe('CrossThread Module', function () { assert.equal(onMessageByTypeStub.callCount, 1); }); - it('should properly clean up resources', function () { + it('should properly clean up resources', () => { // Set up some pending requests collector['awaitingResponses'].set(1, []); collector['awaitingResponses'].set(2, []); @@ -244,10 +240,10 @@ describe('CrossThread Module', function () { }); }); - describe('StatusAggregator', function () { + describe('StatusAggregator', () => { let clock; - beforeEach(function () { + beforeEach(() => { clock = sinon.useFakeTimers(); }); @@ -255,7 +251,7 @@ describe('CrossThread Module', function () { clock.restore(); }); - it('should aggregate single component from single thread', function () { + it('should aggregate single component from single thread', () => { const allStatuses = new Map([ [ 'database@worker-0', @@ -279,7 +275,7 @@ describe('CrossThread Module', function () { assert.equal(aggStatus.abnormalities, undefined); }); - it('should detect abnormalities when statuses differ', function () { + it('should detect abnormalities when statuses differ', () => { const allStatuses = new Map([ [ 'api@worker-0', @@ -322,7 +318,7 @@ describe('CrossThread Module', function () { assert.equal(worker0Abnormality.workerIndex, -1); // No workerIndex in input }); - it('should prioritize non-healthy messages', function () { + it('should prioritize non-healthy messages', () => { const allStatuses = new Map([ [ 'service@worker-0', @@ -357,7 +353,7 @@ describe('CrossThread Module', function () { assert.equal(aggStatus.latestMessage, 'High latency detected'); // Non-healthy message preferred }); - it('should handle status priority correctly', function () { + it('should handle status priority correctly', () => { const testCases = [ { statuses: ['healthy', 'healthy', 'healthy'], expected: 'healthy' }, { statuses: ['healthy', 'unknown', 'healthy'], expected: 'unknown' }, @@ -381,7 +377,7 @@ describe('CrossThread Module', function () { } }); - it('should handle main thread correctly', function () { + it('should handle main thread correctly', () => { const allStatuses = new Map([ [ 'logger@main', @@ -408,7 +404,7 @@ describe('CrossThread Module', function () { assert.equal(aggStatus.lastChecked.workers[1], 2000); }); - it('should handle worker index in status data', function () { + it('should handle worker index in status data', () => { const allStatuses = new Map([ [ 'cache@worker-1', diff --git a/unitTests/components/status/errors.test.ts b/unitTests/components/status/errors.test.js similarity index 77% rename from unitTests/components/status/errors.test.ts rename to unitTests/components/status/errors.test.js index 1e2fe2491..81019941a 100644 --- a/unitTests/components/status/errors.test.ts +++ b/unitTests/components/status/errors.test.js @@ -1,18 +1,18 @@ -import assert from 'node:assert/strict'; -import { +const { describe, it } = require('mocha'); +const assert = require('node:assert/strict'); +const { ComponentStatusError, CrossThreadTimeoutError, ITCError, AggregationError, ComponentStatusOperationError, CrossThreadCollectionError, -} from '@/components/status/errors'; -import { HTTP_STATUS_CODES } from '@/utility/errors/commonErrors'; -import { describe, it } from 'mocha'; +} = require('#src/components/status/errors'); +const { HTTP_STATUS_CODES } = require('#js/utility/errors/commonErrors'); -describe('Component Status Errors', function () { - describe('ComponentStatusError', function () { - it('should create base error with default status code', function () { +describe('Component Status Errors', () => { + describe('ComponentStatusError', () => { + it('should create base error with default status code', () => { const error = new ComponentStatusError('Test error'); assert.equal(error.name, 'ComponentStatusError'); @@ -22,15 +22,15 @@ describe('Component Status Errors', function () { assert.ok(error.stack); }); - it('should create base error with custom status code', function () { + it('should create base error with custom status code', () => { const error = new ComponentStatusError('Bad request', HTTP_STATUS_CODES.BAD_REQUEST); assert.equal(error.statusCode, HTTP_STATUS_CODES.BAD_REQUEST); }); }); - describe('CrossThreadTimeoutError', function () { - it('should create timeout error with details', function () { + describe('CrossThreadTimeoutError', () => { + it('should create timeout error with details', () => { const error = new CrossThreadTimeoutError(123, 5000, 3); assert.equal(error.name, 'CrossThreadTimeoutError'); @@ -44,8 +44,8 @@ describe('Component Status Errors', function () { }); }); - describe('ITCError', function () { - it('should create ITC error without cause', function () { + describe('ITCError', () => { + it('should create ITC error without cause', () => { const error = new ITCError('sendEvent'); assert.equal(error.name, 'ITCError'); @@ -55,7 +55,7 @@ describe('Component Status Errors', function () { assert.ok(error.message.includes('Unknown error')); }); - it('should create ITC error with cause', function () { + it('should create ITC error with cause', () => { const cause = new Error('Network failure'); const error = new ITCError('broadcastStatus', cause); @@ -64,8 +64,8 @@ describe('Component Status Errors', function () { }); }); - describe('AggregationError', function () { - it('should create aggregation error', function () { + describe('AggregationError', () => { + it('should create aggregation error', () => { const cause = new Error('Invalid data'); const error = new AggregationError(10, cause); @@ -77,8 +77,8 @@ describe('Component Status Errors', function () { }); }); - describe('ComponentStatusOperationError', function () { - it('should create operation error', function () { + describe('ComponentStatusOperationError', () => { + it('should create operation error', () => { const error = new ComponentStatusOperationError('my-component', 'setStatus', 'Invalid status level'); assert.equal(error.name, 'ComponentStatusOperationError'); @@ -90,8 +90,8 @@ describe('Component Status Errors', function () { }); }); - describe('CrossThreadCollectionError', function () { - it('should create collection error for partial success', function () { + describe('CrossThreadCollectionError', () => { + it('should create collection error for partial success', () => { const result = { success: true, collectedFromThreads: 5, @@ -110,7 +110,7 @@ describe('Component Status Errors', function () { assert.ok(error.message.includes('3 timed out')); }); - it('should create collection error for complete failure', function () { + it('should create collection error for complete failure', () => { const result = { success: false, collectedFromThreads: 0, @@ -125,7 +125,7 @@ describe('Component Status Errors', function () { assert.ok(error.message.includes('Timeout')); }); - it('should provide detailed diagnostics', function () { + it('should provide detailed diagnostics', () => { const result = { success: true, collectedFromThreads: 3, diff --git a/unitTests/components/status/index.test.ts b/unitTests/components/status/index.test.js similarity index 85% rename from unitTests/components/status/index.test.ts rename to unitTests/components/status/index.test.js index 1fdc98df9..526e8c36a 100644 --- a/unitTests/components/status/index.test.ts +++ b/unitTests/components/status/index.test.js @@ -1,21 +1,15 @@ -import assert from 'node:assert/strict'; -import { statusForComponent, reset, STATUS, internal } from '@/components/status/index'; - +const { describe, it, beforeEach, after } = require('mocha'); +const assert = require('node:assert/strict'); +const { statusForComponent, reset, STATUS, internal } = require('#src/components/status/index'); const { ComponentStatus } = internal; -describe('Component Status API', function () { - beforeEach(function () { - // Clear the registry before each test - reset(); - }); +describe('Component Status API', () => { + beforeEach(reset); - after(function () { - // Clean up the registry after all tests complete - reset(); - }); + after(reset); describe('statusForComponent', function () { - it('should return a ComponentStatusBuilder instance', function () { + it('should return a ComponentStatusBuilder instance', () => { const api = statusForComponent('test-component'); assert.ok(api); assert.equal(typeof api.warning, 'function'); @@ -26,28 +20,28 @@ describe('Component Status API', function () { assert.equal(typeof api.get, 'function'); }); - it('should return the same instance for the same component name', function () { + it('should return the same instance for the same component name', () => { const api1 = statusForComponent('cached-component'); const api2 = statusForComponent('cached-component'); assert.strictEqual(api1, api2); }); - it('should return different instances for different component names', function () { + it('should return different instances for different component names', () => { const api1 = statusForComponent('component-1'); const api2 = statusForComponent('component-2'); assert.notStrictEqual(api1, api2); }); }); - describe('ComponentStatusAPI methods', function () { + describe('ComponentStatusAPI methods', () => { let api; - beforeEach(function () { + beforeEach(() => { api = statusForComponent('test-api-component'); }); describe('warning', function () { - it('should set component status to warning', function () { + it('should set component status to warning', () => { api.warning('High memory usage detected'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -58,7 +52,7 @@ describe('Component Status API', function () { }); describe('error', function () { - it('should set component status to error with message only', function () { + it('should set component status to error with message only', () => { api.error('Connection failed'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -68,7 +62,7 @@ describe('Component Status API', function () { assert.equal(status.error, undefined); }); - it('should set component status to error with message and Error object', function () { + it('should set component status to error with message and Error object', () => { const error = new Error('Database timeout'); api.error('Failed to connect to database', error); @@ -81,7 +75,7 @@ describe('Component Status API', function () { }); describe('healthy', function () { - it('should set component status to healthy with message', function () { + it('should set component status to healthy with message', () => { api.healthy('Component is running smoothly'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -90,7 +84,7 @@ describe('Component Status API', function () { assert.equal(status.message, 'Component is running smoothly'); }); - it('should set component status to healthy without message', function () { + it('should set component status to healthy without message', () => { api.healthy(); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -101,7 +95,7 @@ describe('Component Status API', function () { }); describe('loading', function () { - it('should set component status to loading with message', function () { + it('should set component status to loading with message', () => { api.loading('Initializing database connection'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -110,7 +104,7 @@ describe('Component Status API', function () { assert.equal(status.message, 'Initializing database connection'); }); - it('should set component status to loading without message', function () { + it('should set component status to loading without message', () => { api.loading(); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -121,7 +115,7 @@ describe('Component Status API', function () { }); describe('unknown', function () { - it('should set component status to unknown with message', function () { + it('should set component status to unknown with message', () => { api.unknown('Component state is unclear'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -130,7 +124,7 @@ describe('Component Status API', function () { assert.equal(status.message, 'Component state is unclear'); }); - it('should set component status to unknown without message', function () { + it('should set component status to unknown without message', () => { api.unknown(); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -141,12 +135,12 @@ describe('Component Status API', function () { }); describe('get', function () { - it('should return undefined for non-existent component', function () { + it('should return undefined for non-existent component', () => { const status = api.get(); assert.equal(status, undefined); }); - it('should return the current status of the component', function () { + it('should return the current status of the component', () => { api.healthy('Running'); const status = api.get(); @@ -155,7 +149,7 @@ describe('Component Status API', function () { assert.equal(status.message, 'Running'); }); - it('should reflect status changes', function () { + it('should reflect status changes', () => { // Initially healthy api.healthy('Started'); let status = api.get(); @@ -175,7 +169,7 @@ describe('Component Status API', function () { }); describe('Integration with componentStatusRegistry', function () { - it('should properly integrate with the global registry', function () { + it('should properly integrate with the global registry', () => { const api1 = statusForComponent('integration-test-1'); const api2 = statusForComponent('integration-test-2'); @@ -194,7 +188,7 @@ describe('Component Status API', function () { assert.equal(api2.get().status, STATUS.WARNING); }); - it('should work with registry methods', function () { + it('should work with registry methods', () => { const api = statusForComponent('registry-integration'); // Set status via API @@ -213,7 +207,7 @@ describe('Component Status API', function () { }); describe('Multiple component workflow', function () { - it('should handle multiple components independently', function () { + it('should handle multiple components independently', () => { const authApi = statusForComponent('auth-service'); const dbApi = statusForComponent('database'); const cacheApi = statusForComponent('cache'); @@ -239,7 +233,7 @@ describe('Component Status API', function () { }); describe('Status transition scenarios', function () { - it('should handle component lifecycle transitions', function () { + it('should handle component lifecycle transitions', () => { const api = statusForComponent('lifecycle-component'); // Component starts loading diff --git a/unitTests/components/status/registry.test.ts b/unitTests/components/status/registry.test.js similarity index 67% rename from unitTests/components/status/registry.test.ts rename to unitTests/components/status/registry.test.js index 676c9b8f4..b862c5eff 100644 --- a/unitTests/components/status/registry.test.ts +++ b/unitTests/components/status/registry.test.js @@ -1,30 +1,24 @@ -import assert from 'node:assert/strict'; -import { ComponentStatusRegistry } from '@/components/status/ComponentStatusRegistry'; -import { describe, it, before, after } from 'mocha'; - -describe('componentStatusRegistry singleton', function () { - let componentStatusRegistry; - - before(() => { - const registry = require('@/components/status/registry'); - componentStatusRegistry = registry.componentStatusRegistry; - }); +const { describe, it, after } = require('mocha'); +const assert = require('node:assert/strict'); +const { ComponentStatusRegistry } = require('#src/components/status/ComponentStatusRegistry'); +const { componentStatusRegistry } = require('#src/components/status/registry'); +describe('componentStatusRegistry singleton', () => { after(() => { // Clean up the global singleton after tests componentStatusRegistry.reset(); }); - it('should export a ComponentStatusRegistry instance', function () { + it('should export a ComponentStatusRegistry instance', () => { assert.ok(componentStatusRegistry instanceof ComponentStatusRegistry); }); - it('should be a singleton instance', function () { - const { componentStatusRegistry: registry2 } = require('@/components/status/registry'); + it('should be a singleton instance', () => { + const { componentStatusRegistry: registry2 } = require('#src/components/status/registry'); assert.strictEqual(componentStatusRegistry, registry2); }); - it('should have all ComponentStatusRegistry methods', function () { + it('should have all ComponentStatusRegistry methods', () => { // Check core methods exist assert.equal(typeof componentStatusRegistry.reset, 'function'); assert.equal(typeof componentStatusRegistry.setStatus, 'function'); @@ -40,7 +34,7 @@ describe('componentStatusRegistry singleton', function () { assert.equal(typeof componentStatusRegistry.getStatusSummary, 'function'); }); - it('should work with basic operations', function () { + it('should work with basic operations', () => { // Clean up any existing state componentStatusRegistry.reset(); diff --git a/unitTests/components/waitFor.js b/unitTests/components/waitFor.js new file mode 100644 index 000000000..9254b2a00 --- /dev/null +++ b/unitTests/components/waitFor.js @@ -0,0 +1,14 @@ +const assert = require('node:assert/strict'); +const { setTimeout } = require('node:timers/promises'); + +async function waitFor(condition, timeout = 1000, interval = 100) { + let time = 0; + while (!condition()) { + await setTimeout(interval); + if ((time += interval) > timeout) { + assert.fail('Timeout waiting for condition'); + } + } +} + +module.exports = { waitFor }; diff --git a/unitTests/components/waitFor.ts b/unitTests/components/waitFor.ts deleted file mode 100644 index 4eafacf2e..000000000 --- a/unitTests/components/waitFor.ts +++ /dev/null @@ -1,12 +0,0 @@ -import assert from 'node:assert/strict'; -import { setTimeout } from 'node:timers/promises'; - -export async function waitFor(condition, timeout = 1000, interval = 100) { - let time = 0; - while (!condition()) { - await setTimeout(interval); - if ((time += interval) > timeout) { - assert.fail('Timeout waiting for condition'); - } - } -} diff --git a/unitTests/testUtils.js b/unitTests/testUtils.js new file mode 100644 index 000000000..892d895b5 --- /dev/null +++ b/unitTests/testUtils.js @@ -0,0 +1,82 @@ +const path = require('node:path'); +const { isMainThread } = require('node:worker_threads'); +const env = require('#js/utility/environment/environmentManager'); +const terms = require('#src/utility/hdbTerms'); +const { resetDatabases } = require('#src/resources/databases'); +const fs = require('fs-extra'); +const { stringify } = require('yaml'); +const { setHdbBasePath } = require('#js/utility/environment/environmentManager'); + +const UNIT_TEST_DIR = __dirname; +const ENV_DIR_NAME = 'testEnv'; +const ENV_DIR_PATH = path.join(UNIT_TEST_DIR, ENV_DIR_NAME); + +async function tearDownMockDB(envs = undefined, partial_teardown = false) { + try { + if (envs !== undefined) { + await Promise.all(envs.map((table) => table.delete())).catch(); + } + + delete global.hdb_schema; + global.lmdb_map = undefined; + if (!partial_teardown) { + await fs.remove(ENV_DIR_PATH); + } + } catch (err) {} +} + +function getMockLMDBPath() { + const lmdbPath = path.join(UNIT_TEST_DIR, ENV_DIR_NAME, process.pid.toString()); + // TODO: Setting the "root" path some more; should clean this up! + env.setProperty(terms.HDB_SETTINGS_NAMES.HDB_ROOT_KEY, lmdbPath); + env.setProperty(terms.CONFIG_PARAMS.DATABASES, { data: { path: lmdbPath }, dev: { path: lmdbPath } }); + resetDatabases(); + if (isMainThread) { + process.on('exit', () => tearDownMockDB()); + } + return lmdbPath; +} + +function createTestSandbox() { + // TODO: Seems like we're setting the "root" path over and over again + // We should clean this up and make it so you only have to set it once + const lmdbPath = getMockLMDBPath(); // we set it in here + process.env.ROOTPATH = lmdbPath; // setting it again + setHdbBasePath(lmdbPath); // also setting the root path + const storagePath = path.join(lmdbPath, 'database'); + process.env.STORAGE_PATH = storagePath; // another "root-ish" path we have to set + const bootPropsPath = path.join(lmdbPath, terms.HDB_HOME_DIR_NAME); + fs.mkdirpSync(storagePath); + fs.writeFileSync( + path.join(lmdbPath, 'harperdb-config.yaml'), + stringify({ + rootPath: lmdbPath, + logging: { + path: path.join(lmdbPath, 'logs'), + file: false, + stdStreams: false, + auditLog: false, + }, + localStudio: {}, + operationsApi: {}, + http: {}, + storage: { writeAsync: false }, + }) + ); + fs.mkdirpSync(bootPropsPath); + fs.writeFileSync( + path.join(bootPropsPath, terms.BOOT_PROPS_FILE_NAME), + `settings_path = ${lmdbPath}/harperdb-config.yaml` + ); + return lmdbPath; +} + +function cleanupTestSandbox() { + return tearDownMockDB(); +} + +module.exports = { + getMockLMDBPath, + createTestSandbox, + cleanupTestSandbox, +}; diff --git a/unitTests/tsconfig.json b/unitTests/tsconfig.json deleted file mode 100644 index 52415630e..000000000 --- a/unitTests/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "include": ["./**/*"], - "compilerOptions": { - // Provide convenient path alias for compiled output so its relative to root - // Meaning imports like "@/components/Component" will just work! - // Change this to ../* once we're fully in TypeStrip land - "baseUrl": "..", - "paths": { - "@/*": ["./dist/*"] - } - } -} From cc18bd88efa72cc50b09282f2ffabda234422b88 Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Tue, 23 Dec 2025 17:14:28 -0700 Subject: [PATCH 3/5] Add Typestrip unit test runners These don't all pass yet, but nice to have to find places we need to fix up. --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 07c893074..1294e2d71 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "test:integration:api-tests": "node --test integrationTests/apiTests/tests/testSuite.mjs", "test:integration": "node integrationTests/utils/scripts/run.ts", "test:unit": "mocha --config unitTests/.mocharc.json", - "test:unit:all": "npm run test:unit unitTests" + "test:unit:all": "npm run test:unit unitTests", + "test:unit:typestrip": "env NODE_OPTIONS='--conditions=typestrip' npm run test:unit", + "test:unit:typestrip:all": "npm run test:unit:typestrip unitTests" }, "imports": { "#src/*": { From 75dc5926296d41b11357696e01503a618a3e0955 Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Tue, 23 Dec 2025 17:29:21 -0700 Subject: [PATCH 4/5] Fix incomplete import alias fix --- utility/processManagement/processManagement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utility/processManagement/processManagement.js b/utility/processManagement/processManagement.js index 68ffbca00..88db3c97b 100644 --- a/utility/processManagement/processManagement.js +++ b/utility/processManagement/processManagement.js @@ -121,7 +121,7 @@ function restartHdb() { function getHdbPid() { const harperPath = envMangr.getHdbBasePath(); if (!harperPath) return; - const pidFile = path.join(harperPath, terms.HDB_PID_FILE); + const pidFile = path.join(harperPath, hdbTerms.HDB_PID_FILE); const hdbPid = readPidFile(pidFile); // If the pid file doesn't exist or the pid is the same as the current process, return. // In a Docker container, the pid is usually 1, and so if a previous process crashed, there will still From 630b499cb3a2165c6a30a65aa7d606f3c6932cc5 Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Mon, 5 Jan 2026 16:06:57 -0700 Subject: [PATCH 5/5] Do an even more minimal migration of component unit tests --- unitTests/components/Component.test.js | 2 +- unitTests/components/ComponentV1.test.js | 4 +- unitTests/components/EntryHandler.test.js | 1 - unitTests/components/OptionsWatcher.test.js | 2 +- unitTests/components/Scope.test.js | 45 ++++- unitTests/components/componentLoader.test.js | 42 ++--- .../deriveCommonPatternBase.test.js | 1 - unitTests/components/deriveGlobOption.test.js | 1 - unitTests/components/requestRestart.test.js | 1 - .../components/resolveBaseURLPath.test.js | 1 - .../components/status/ComponentStatus.test.js | 75 +++++---- .../status/ComponentStatusRegistry.test.js | 155 +++++++++--------- unitTests/components/status/api.test.js | 47 +++--- .../components/status/crossThread.test.js | 52 +++--- unitTests/components/status/errors.test.js | 35 ++-- unitTests/components/status/index.test.js | 55 ++++--- unitTests/components/status/registry.test.js | 13 +- 17 files changed, 282 insertions(+), 250 deletions(-) diff --git a/unitTests/components/Component.test.js b/unitTests/components/Component.test.js index 2b16c58f3..5f1169884 100644 --- a/unitTests/components/Component.test.js +++ b/unitTests/components/Component.test.js @@ -1,4 +1,4 @@ -const { describe, it } = require('mocha'); +/* eslint-disable sonarjs/no-nested-functions */ const { Component, ComponentInvalidPatternError } = require('#src/components/Component'); const assert = require('node:assert/strict'); diff --git a/unitTests/components/ComponentV1.test.js b/unitTests/components/ComponentV1.test.js index 093c44c17..aa0d0cd54 100644 --- a/unitTests/components/ComponentV1.test.js +++ b/unitTests/components/ComponentV1.test.js @@ -1,4 +1,6 @@ -const { describe, it, beforeEach, afterEach, after } = require('mocha'); +/* eslint-disable @typescript-eslint/no-require-imports, sonarjs/void-use, sonarjs/assertions-in-tests, sonarjs/no-nested-functions */ +// Not sure why sonar cannot pick up the assertions within the tests. I think its because they are `async`. + const { tmpdir } = require('node:os'); const { processResourceExtensionComponent, diff --git a/unitTests/components/EntryHandler.test.js b/unitTests/components/EntryHandler.test.js index 7ed51b301..09d6c90f1 100644 --- a/unitTests/components/EntryHandler.test.js +++ b/unitTests/components/EntryHandler.test.js @@ -1,4 +1,3 @@ -const { describe, it, beforeEach, afterEach } = require('mocha'); const { EntryHandler } = require('#src/components/EntryHandler'); const { EventEmitter, once } = require('node:events'); const assert = require('node:assert/strict'); diff --git a/unitTests/components/OptionsWatcher.test.js b/unitTests/components/OptionsWatcher.test.js index f02e7062e..3a1989900 100644 --- a/unitTests/components/OptionsWatcher.test.js +++ b/unitTests/components/OptionsWatcher.test.js @@ -1,4 +1,4 @@ -const { describe, it, beforeEach, afterEach } = require('mocha'); +/* eslint-disable sonarjs/no-nested-functions */ const { OptionsWatcher } = require('#src/components/OptionsWatcher'); const { EventEmitter, once } = require('node:events'); const assert = require('node:assert/strict'); diff --git a/unitTests/components/Scope.test.js b/unitTests/components/Scope.test.js index c66c01dd4..549e41ed6 100644 --- a/unitTests/components/Scope.test.js +++ b/unitTests/components/Scope.test.js @@ -1,4 +1,3 @@ -const { describe, it, before, beforeEach, after, afterEach } = require('mocha'); const { Scope, MissingDefaultFilesOptionError } = require('#src/components/Scope'); const { EventEmitter } = require('node:events'); const assert = require('node:assert/strict'); @@ -13,12 +12,8 @@ const { EntryHandler } = require('#src/components/EntryHandler'); const { restartNeeded, resetRestartNeeded } = require('#src/components/requestRestart'); const { writeFile } = require('node:fs/promises'); const { waitFor } = require('./waitFor.js'); -const { createTestSandbox, cleanupTestSandbox } = require('../testUtils.js'); describe('Scope', () => { - before(createTestSandbox); - after(cleanupTestSandbox); - beforeEach(() => { this.resources = new Resources(); this.server = {}; @@ -148,7 +143,10 @@ describe('Scope', () => { await scope.ready; - await scope.handleEntry().ready; + const entryHandler = scope.handleEntry(); + + // Wait for initial load to complete - the default behavior will trigger restart + await entryHandler.ready; assert.equal(restartNeeded(), true, 'requestRestart was called'); @@ -162,7 +160,7 @@ describe('Scope', () => { await scope.ready; - await scope.handleEntry(() => {}).ready; + scope.handleEntry(() => {}); assert.equal(restartNeeded(), false, 'requestRestart was not called'); @@ -219,6 +217,9 @@ describe('Scope', () => { const customEntryHandlerPathOnlyArg = scope.handleEntry('.'); assert.ok(customEntryHandlerPathOnlyArg instanceof EntryHandler, 'Custom entry handler should be created'); + // Reset restart flag - the first handler without a function triggers restart when it encounters files + resetRestartNeeded(); + const customEntryHandlerPathAndFunctionArgs = scope.handleEntry('.', () => {}); assert.ok(customEntryHandlerPathAndFunctionArgs instanceof EntryHandler, 'Custom entry handler should be created'); @@ -235,4 +236,34 @@ describe('Scope', () => { assert.equal(entryHandleCloseSpy1.callCount, 1, 'close event for custom entry handler should be emitted once'); assert.equal(entryHandleCloseSpy2.callCount, 1, 'close event for custom entry handler should be emitted once'); }); + + it('should support synchronous handleEntry with event-based initial load tracking', async () => { + writeFileSync(this.configFilePath, stringify({ [this.name]: { files: 'test.js' } })); + + const scope = new Scope(this.name, this.directory, this.configFilePath, this.resources, this.server); + + await scope.ready; + + const handleEntrySpy = spy(); + + // Call handleEntry - returns EntryHandler immediately + const entryHandler = scope.handleEntry(handleEntrySpy); + + // Should return an EntryHandler immediately (not a Promise) + assert.ok(entryHandler instanceof EntryHandler, 'handleEntry should return EntryHandler synchronously'); + + // Can listen for the ready event if needed + const readySpy = spy(); + entryHandler.on('ready', readySpy); + + // Wait for initial load to complete + await entryHandler.ready; + assert.ok(readySpy.calledOnce, 'ready event should be emitted once'); + + // Handler should be called for initial files + await waitFor(() => handleEntrySpy.callCount > 0); + assert.ok(handleEntrySpy.callCount > 0, 'Entry handler should be called'); + + scope.close(); + }); }); diff --git a/unitTests/components/componentLoader.test.js b/unitTests/components/componentLoader.test.js index 5ae7f04da..c44e2321b 100644 --- a/unitTests/components/componentLoader.test.js +++ b/unitTests/components/componentLoader.test.js @@ -1,20 +1,16 @@ -const { describe, it, before, after, beforeEach } = require('mocha'); const assert = require('node:assert/strict'); const sinon = require('sinon'); const path = require('path'); const { tmpdir } = require('os'); const { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } = require('fs'); -const { createTestSandbox, cleanupTestSandbox } = require('../testUtils.js'); -describe('ComponentLoader Status Integration', () => { +describe('ComponentLoader Status Integration', function () { let componentStatusRegistry; let tempDir; let componentLoader; let lifecycle; - before(() => { - createTestSandbox(); - + before(function () { // Create a temporary directory for test components tempDir = mkdtempSync(path.join(tmpdir(), 'harper-test-components-')); @@ -61,7 +57,7 @@ describe('ComponentLoader Status Integration', () => { componentLoader = require('#src/components/componentLoader'); }); - after(async () => { + after(function () { // Restore all spies sinon.restore(); @@ -72,11 +68,9 @@ describe('ComponentLoader Status Integration', () => { // Clear the component status registry componentStatusRegistry.reset(); - - await cleanupTestSandbox(); }); - beforeEach(() => { + beforeEach(function () { // Reset spy history before each test lifecycle.loading.resetHistory(); lifecycle.loaded.resetHistory(); @@ -91,8 +85,8 @@ describe('ComponentLoader Status Integration', () => { componentStatusRegistry.reset(); }); - describe('Basic component status tracking', () => { - it('should initialize loading status for non-root components', async () => { + describe('Basic component status tracking', function () { + it('should initialize loading status for non-root components', async function () { // Create a test component directory const componentDirName = 'test-component'; const componentDir = path.join(tempDir, componentDirName); @@ -126,7 +120,7 @@ describe('ComponentLoader Status Integration', () => { ); }); - it('should track loading for components with trusted loaders', async () => { + it('should track loading for components with trusted loaders', async function () { // Create a component using a trusted loader const componentDirName = 'trusted-component'; const componentDir = path.join(tempDir, componentDirName); @@ -159,7 +153,7 @@ describe('ComponentLoader Status Integration', () => { assert.match(loadedCalls[0].args[1], /loaded successfully/); }); - it('should mark component as failed when it loads no functionality', async () => { + it('should mark component as failed when it loads no functionality', async function () { // Create a component directory without config // This will use DEFAULT_CONFIG but won't actually load anything const componentDirName = 'empty-component'; @@ -193,8 +187,8 @@ describe('ComponentLoader Status Integration', () => { }); }); - describe('Component status verification', () => { - it('should properly set status in registry after successful load', async () => { + describe('Component status verification', function () { + it('should properly set status in registry after successful load', async function () { // Create a component const componentDirName = 'verify-status'; const componentDir = path.join(tempDir, componentDirName); @@ -218,19 +212,19 @@ describe('ComponentLoader Status Integration', () => { assert.ok(status.message, 'Should have a status message'); }); - it('should handle component loading errors gracefully', async () => { + it('should handle component loading errors gracefully', async function () { // Stub the dataLoader module's handleApplication method to throw an error - // const dataLoaderModule = require('#src/resources/dataLoader'); - // const originalhandleApplication = dataLoaderModule.handleApplication; - // sinon.stub(dataLoaderModule, 'handleApplication').throws(new Error('DataLoader failed to initialize')); + const dataLoaderModule = require('#src/resources/dataLoader'); + const originalhandleApplication = dataLoaderModule.handleApplication; + sinon.stub(dataLoaderModule, 'handleApplication').throws(new Error('DataLoader failed to initialize')); // Create a component that uses dataLoader const componentDirName = 'error-component'; const componentDir = path.join(tempDir, componentDirName); mkdirSync(componentDir); - // Create a bad dataLoader config - writeFileSync(path.join(componentDir, 'harperdb-config.yaml'), 'dataLoader:\n nope: \ninvalid'); + // Create config that uses dataLoader + writeFileSync(path.join(componentDir, 'harperdb-config.yaml'), 'dataLoader:\n path: "data"'); // Create mock resources const mockResources = { @@ -253,7 +247,7 @@ describe('ComponentLoader Status Integration', () => { assert.ok(componentFailure.args[1] instanceof Error, 'Should have passed an Error object'); assert.match( String(componentFailure.args[1]), - /YAMLParseError: Could not load component 'dataLoader' for application 'error-component'/, + /DataLoader failed to initialize/, 'Error should contain our error message' ); @@ -265,7 +259,7 @@ describe('ComponentLoader Status Integration', () => { assert.ok(errorCall, 'Should have created an ErrorResource'); // Restore the original handleApplication method - // dataLoaderModule.handleApplication = originalhandleApplication; + dataLoaderModule.handleApplication = originalhandleApplication; }); }); }); diff --git a/unitTests/components/deriveCommonPatternBase.test.js b/unitTests/components/deriveCommonPatternBase.test.js index cdb0e3d7b..bb80baf46 100644 --- a/unitTests/components/deriveCommonPatternBase.test.js +++ b/unitTests/components/deriveCommonPatternBase.test.js @@ -1,4 +1,3 @@ -const { describe, it } = require('mocha'); const { deriveCommonPatternBase } = require('#src/components/deriveCommonPatternBase'); const assert = require('node:assert/strict'); diff --git a/unitTests/components/deriveGlobOption.test.js b/unitTests/components/deriveGlobOption.test.js index 23179c516..97271691a 100644 --- a/unitTests/components/deriveGlobOption.test.js +++ b/unitTests/components/deriveGlobOption.test.js @@ -1,4 +1,3 @@ -const { describe, it } = require('mocha'); const assert = require('node:assert/strict'); const { deriveGlobOptions } = require('#src/components/deriveGlobOptions'); diff --git a/unitTests/components/requestRestart.test.js b/unitTests/components/requestRestart.test.js index a0ecdae86..b384b42c4 100644 --- a/unitTests/components/requestRestart.test.js +++ b/unitTests/components/requestRestart.test.js @@ -1,4 +1,3 @@ -const { describe, it } = require('mocha'); const { requestRestart, restartNeeded } = require('#src/components/requestRestart'); const assert = require('node:assert/strict'); diff --git a/unitTests/components/resolveBaseURLPath.test.js b/unitTests/components/resolveBaseURLPath.test.js index 4af848277..9d0dae502 100644 --- a/unitTests/components/resolveBaseURLPath.test.js +++ b/unitTests/components/resolveBaseURLPath.test.js @@ -1,4 +1,3 @@ -const { describe, it } = require('mocha'); const { resolveBaseURLPath, InvalidBaseURLPathError } = require('#src/components/resolveBaseURLPath'); const assert = require('node:assert/strict'); diff --git a/unitTests/components/status/ComponentStatus.test.js b/unitTests/components/status/ComponentStatus.test.js index 907016c75..19e30e287 100644 --- a/unitTests/components/status/ComponentStatus.test.js +++ b/unitTests/components/status/ComponentStatus.test.js @@ -1,23 +1,22 @@ -const { describe, it, beforeEach, afterEach } = require('mocha'); const assert = require('node:assert/strict'); const sinon = require('sinon'); const { ComponentStatus } = require('#src/components/status/ComponentStatus'); const { COMPONENT_STATUS_LEVELS } = require('#src/components/status/types'); -describe('ComponentStatus', () => { +describe('ComponentStatus', function () { let clock; - beforeEach(() => { + beforeEach(function () { // Use fake timers to control Date objects clock = sinon.useFakeTimers(); }); - afterEach(() => { + afterEach(function () { clock.restore(); }); - describe('constructor', () => { - it('should create a ComponentStatus with all parameters', () => { + describe('constructor', function () { + it('should create a ComponentStatus with all parameters', function () { const error = new Error('Test error'); const status = new ComponentStatus('error', 'Component failed', error); @@ -28,7 +27,7 @@ describe('ComponentStatus', () => { assert.equal(status.lastChecked.getTime(), 0); // fake timer starts at 0 }); - it('should create a ComponentStatus without optional parameters', () => { + it('should create a ComponentStatus without optional parameters', function () { const status = new ComponentStatus('healthy'); assert.equal(status.status, 'healthy'); @@ -37,15 +36,15 @@ describe('ComponentStatus', () => { assert.ok(status.lastChecked instanceof Date); }); - it('should accept string as error', () => { + it('should accept string as error', function () { const status = new ComponentStatus('error', 'Component failed', 'String error'); assert.equal(status.error, 'String error'); }); }); - describe('updateStatus', () => { - it('should update status and message', () => { + describe('updateStatus', function () { + it('should update status and message', function () { const status = new ComponentStatus('loading', 'Starting up'); // Advance time @@ -58,7 +57,7 @@ describe('ComponentStatus', () => { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should clear error when status is not ERROR', () => { + it('should clear error when status is not ERROR', function () { const error = new Error('Test error'); const status = new ComponentStatus('error', 'Failed', error); @@ -67,7 +66,7 @@ describe('ComponentStatus', () => { assert.equal(status.error, undefined); }); - it('should keep error when status remains ERROR', () => { + it('should keep error when status remains ERROR', function () { const error = new Error('Test error'); const status = new ComponentStatus('error', 'Failed', error); @@ -76,7 +75,7 @@ describe('ComponentStatus', () => { assert.equal(status.error, error); }); - it('should update without message', () => { + it('should update without message', function () { const status = new ComponentStatus('loading'); status.updateStatus('healthy'); @@ -86,8 +85,8 @@ describe('ComponentStatus', () => { }); }); - describe('markHealthy', () => { - it('should set status to healthy with custom message', () => { + describe('markHealthy', function () { + it('should set status to healthy with custom message', function () { const status = new ComponentStatus('loading'); clock.tick(1000); @@ -98,7 +97,7 @@ describe('ComponentStatus', () => { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should set status to healthy with default message', () => { + it('should set status to healthy with default message', function () { const status = new ComponentStatus('error'); status.markHealthy(); @@ -107,7 +106,7 @@ describe('ComponentStatus', () => { assert.equal(status.message, 'Component is healthy'); }); - it('should clear error when marking healthy', () => { + it('should clear error when marking healthy', function () { const status = new ComponentStatus('error', 'Failed', new Error('Test')); status.markHealthy(); @@ -116,8 +115,8 @@ describe('ComponentStatus', () => { }); }); - describe('markError', () => { - it('should set status to error with Error object', () => { + describe('markError', function () { + it('should set status to error with Error object', function () { const status = new ComponentStatus('healthy'); const error = new Error('Something went wrong'); @@ -130,7 +129,7 @@ describe('ComponentStatus', () => { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should set status to error with string error', () => { + it('should set status to error with string error', function () { const status = new ComponentStatus('healthy'); status.markError('String error message'); @@ -140,7 +139,7 @@ describe('ComponentStatus', () => { assert.equal(status.message, 'String error message'); }); - it('should use error message when no custom message provided', () => { + it('should use error message when no custom message provided', function () { const status = new ComponentStatus('healthy'); const error = new Error('Error from exception'); @@ -150,8 +149,8 @@ describe('ComponentStatus', () => { }); }); - describe('markWarning', () => { - it('should set status to warning with message', () => { + describe('markWarning', function () { + it('should set status to warning with message', function () { const status = new ComponentStatus('healthy'); clock.tick(1000); @@ -162,7 +161,7 @@ describe('ComponentStatus', () => { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should clear error when marking warning', () => { + it('should clear error when marking warning', function () { const status = new ComponentStatus('error', 'Failed', new Error('Test')); status.markWarning('Recovered with warnings'); @@ -171,8 +170,8 @@ describe('ComponentStatus', () => { }); }); - describe('markLoading', () => { - it('should set status to loading with custom message', () => { + describe('markLoading', function () { + it('should set status to loading with custom message', function () { const status = new ComponentStatus('unknown'); clock.tick(1000); @@ -183,7 +182,7 @@ describe('ComponentStatus', () => { assert.equal(status.lastChecked.getTime(), 1000); }); - it('should set status to loading with default message', () => { + it('should set status to loading with default message', function () { const status = new ComponentStatus('unknown'); status.markLoading(); @@ -193,8 +192,8 @@ describe('ComponentStatus', () => { }); }); - describe('status check methods', () => { - it('should correctly identify healthy status', () => { + describe('status check methods', function () { + it('should correctly identify healthy status', function () { const healthyStatus = new ComponentStatus('healthy'); const errorStatus = new ComponentStatus('error'); @@ -202,7 +201,7 @@ describe('ComponentStatus', () => { assert.equal(errorStatus.isHealthy(), false); }); - it('should correctly identify error status', () => { + it('should correctly identify error status', function () { const errorStatus = new ComponentStatus('error'); const healthyStatus = new ComponentStatus('healthy'); @@ -210,7 +209,7 @@ describe('ComponentStatus', () => { assert.equal(healthyStatus.hasError(), false); }); - it('should correctly identify loading status', () => { + it('should correctly identify loading status', function () { const loadingStatus = new ComponentStatus('loading'); const healthyStatus = new ComponentStatus('healthy'); @@ -218,7 +217,7 @@ describe('ComponentStatus', () => { assert.equal(healthyStatus.isLoading(), false); }); - it('should correctly identify warning status', () => { + it('should correctly identify warning status', function () { const warningStatus = new ComponentStatus('warning'); const healthyStatus = new ComponentStatus('healthy'); @@ -227,20 +226,20 @@ describe('ComponentStatus', () => { }); }); - describe('getSummary', () => { - it('should return summary with message', () => { + describe('getSummary', function () { + it('should return summary with message', function () { const status = new ComponentStatus('error', 'Database connection failed'); assert.equal(status.getSummary(), 'ERROR: Database connection failed'); }); - it('should return summary without message', () => { + it('should return summary without message', function () { const status = new ComponentStatus('healthy'); assert.equal(status.getSummary(), 'HEALTHY'); }); - it('should handle all status levels', () => { + it('should handle all status levels', function () { const statusLevels = ['healthy', 'warning', 'error', 'loading', 'unknown']; for (const level of statusLevels) { @@ -250,8 +249,8 @@ describe('ComponentStatus', () => { }); }); - describe('status transitions', () => { - it('should transition through multiple states correctly', () => { + describe('status transitions', function () { + it('should transition through multiple states correctly', function () { const status = new ComponentStatus('unknown'); // Unknown -> Loading diff --git a/unitTests/components/status/ComponentStatusRegistry.test.js b/unitTests/components/status/ComponentStatusRegistry.test.js index fc4509fe0..69a39b2c0 100644 --- a/unitTests/components/status/ComponentStatusRegistry.test.js +++ b/unitTests/components/status/ComponentStatusRegistry.test.js @@ -1,37 +1,40 @@ -const { describe, it, beforeEach, afterEach, after } = require('mocha'); const assert = require('node:assert/strict'); const sinon = require('sinon'); const { ComponentStatusRegistry } = require('#src/components/status/ComponentStatusRegistry'); const { ComponentStatus } = require('#src/components/status/ComponentStatus'); const { COMPONENT_STATUS_LEVELS } = require('#src/components/status/types'); const { StatusAggregator } = require('#src/components/status/crossThread'); +const itcModule = require('#js/server/threads/itc'); const manageThreadsModule = require('#js/server/threads/manageThreads'); -describe('ComponentStatusRegistry', () => { +describe('ComponentStatusRegistry', function () { let registry; let clock; - beforeEach(() => { + beforeEach(function () { registry = new ComponentStatusRegistry(); clock = sinon.useFakeTimers(); + // Stub ITC functions + sinon.stub(itcModule, 'sendItcEvent').resolves(); sinon.stub(manageThreadsModule, 'onMessageByType'); sinon.stub(manageThreadsModule, 'getWorkerIndex').returns(0); }); - afterEach(() => { + afterEach(function () { clock.restore(); sinon.restore(); + // Reset the registry to ensure clean state registry.reset(); }); - after(() => { + after(function () { // Clean up any environment variables that might have been set delete process.env.COMPONENT_STATUS_TIMEOUT; }); - describe('reset', () => { - it('should clear all statuses', () => { + describe('reset', function () { + it('should clear all statuses', function () { registry.setStatus('comp1', 'healthy', 'All good'); registry.setStatus('comp2', 'error', 'Failed'); @@ -43,8 +46,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('setStatus', () => { - it('should set component status with all parameters', () => { + describe('setStatus', function () { + it('should set component status with all parameters', function () { const error = new Error('Test error'); registry.setStatus('database', 'error', 'Connection failed', error); @@ -55,7 +58,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(status.error, error); }); - it('should set component status without optional parameters', () => { + it('should set component status without optional parameters', function () { registry.setStatus('cache', 'healthy'); const status = registry.getStatus('cache'); @@ -64,7 +67,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(status.error, undefined); }); - it('should overwrite existing status', () => { + it('should overwrite existing status', function () { registry.setStatus('api', 'loading', 'Starting up'); registry.setStatus('api', 'healthy', 'Ready'); @@ -73,7 +76,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(status.message, 'Ready'); }); - it('should throw error for invalid component name', () => { + it('should throw error for invalid component name', function () { assert.throws(() => registry.setStatus('', 'healthy'), { name: 'ComponentStatusOperationError', message: /Component name must be a non-empty string/, @@ -85,7 +88,7 @@ describe('ComponentStatusRegistry', () => { }); }); - it('should throw error for invalid status level', () => { + it('should throw error for invalid status level', function () { assert.throws(() => registry.setStatus('comp4', 'invalid-status'), { name: 'ComponentStatusOperationError', message: /Invalid status level: invalid-status/, @@ -93,12 +96,12 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('getStatus', () => { - it('should return undefined for non-existent component', () => { + describe('getStatus', function () { + it('should return undefined for non-existent component', function () { assert.equal(registry.getStatus('non-existent'), undefined); }); - it('should return ComponentStatus instance', () => { + it('should return ComponentStatus instance', function () { registry.setStatus('test', 'healthy'); const status = registry.getStatus('test'); @@ -106,14 +109,14 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('getAllStatuses', () => { - it('should return empty map initially', () => { + describe('getAllStatuses', function () { + it('should return empty map initially', function () { const statuses = registry.getAllStatuses(); assert.ok(statuses instanceof Map); assert.equal(statuses.size, 0); }); - it('should return all registered statuses', () => { + it('should return all registered statuses', function () { registry.setStatus('comp1', 'healthy'); registry.setStatus('comp2', 'warning', 'High memory'); registry.setStatus('comp3', 'error', 'Failed'); @@ -126,8 +129,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('reportHealthy', () => { - it('should set status to healthy with message', () => { + describe('reportHealthy', function () { + it('should set status to healthy with message', function () { registry.reportHealthy('service', 'Running smoothly'); const status = registry.getStatus('service'); @@ -135,7 +138,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(status.message, 'Running smoothly'); }); - it('should set status to healthy without message', () => { + it('should set status to healthy without message', function () { registry.reportHealthy('service'); const status = registry.getStatus('service'); @@ -143,8 +146,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('reportError', () => { - it('should set status to error with Error object', () => { + describe('reportError', function () { + it('should set status to error with Error object', function () { const error = new Error('Connection timeout'); registry.reportError('database', error, 'DB connection failed'); @@ -154,7 +157,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(status.error, error); }); - it('should set status to error with string error', () => { + it('should set status to error with string error', function () { registry.reportError('api', 'Invalid configuration'); const status = registry.getStatus('api'); @@ -163,8 +166,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('reportWarning', () => { - it('should set status to warning with message', () => { + describe('reportWarning', function () { + it('should set status to warning with message', function () { registry.reportWarning('cache', 'Cache size approaching limit'); const status = registry.getStatus('cache'); @@ -173,9 +176,9 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('lifecycle management methods', () => { - describe('initializeLoading', () => { - it('should set status to loading with custom message', () => { + describe('lifecycle management methods', function () { + describe('initializeLoading', function () { + it('should set status to loading with custom message', function () { registry.initializeLoading('auth', 'Connecting to auth server'); const status = registry.getStatus('auth'); @@ -183,7 +186,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(status.message, 'Connecting to auth server'); }); - it('should set status to loading with default message', () => { + it('should set status to loading with default message', function () { registry.initializeLoading('auth'); const status = registry.getStatus('auth'); @@ -192,8 +195,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('markLoaded', () => { - it('should set status to healthy with custom message', () => { + describe('markLoaded', function () { + it('should set status to healthy with custom message', function () { registry.markLoaded('storage', 'Storage initialized'); const status = registry.getStatus('storage'); @@ -201,7 +204,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(status.message, 'Storage initialized'); }); - it('should set status to healthy with default message', () => { + it('should set status to healthy with default message', function () { registry.markLoaded('storage'); const status = registry.getStatus('storage'); @@ -210,8 +213,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('markFailed', () => { - it('should set status to error with all parameters', () => { + describe('markFailed', function () { + it('should set status to error with all parameters', function () { const error = new Error('Init failed'); registry.markFailed('logger', error, 'Failed to initialize logger'); @@ -221,7 +224,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(status.error, error); }); - it('should set status to error with string error', () => { + it('should set status to error with string error', function () { registry.markFailed('logger', 'Configuration missing'); const status = registry.getStatus('logger'); @@ -231,8 +234,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('getComponentsByStatus', () => { - beforeEach(() => { + describe('getComponentsByStatus', function () { + beforeEach(function () { registry.setStatus('comp1', 'healthy'); registry.setStatus('comp2', 'error', 'Failed'); registry.setStatus('comp3', 'healthy'); @@ -240,19 +243,19 @@ describe('ComponentStatusRegistry', () => { registry.setStatus('comp5', 'error', 'Timeout'); }); - it('should return components with specific status', () => { + it('should return components with specific status', function () { const healthyComponents = registry.getComponentsByStatus(COMPONENT_STATUS_LEVELS.HEALTHY); assert.equal(healthyComponents.length, 2); assert.equal(healthyComponents[0].name, 'comp1'); assert.equal(healthyComponents[1].name, 'comp3'); }); - it('should return empty array for status with no components', () => { + it('should return empty array for status with no components', function () { const loadingComponents = registry.getComponentsByStatus(COMPONENT_STATUS_LEVELS.LOADING); assert.equal(loadingComponents.length, 0); }); - it('should return correct component objects', () => { + it('should return correct component objects', function () { const errorComponents = registry.getComponentsByStatus(COMPONENT_STATUS_LEVELS.ERROR); assert.equal(errorComponents.length, 2); @@ -266,8 +269,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('getStatusSummary', () => { - it('should return initial summary with zero counts', () => { + describe('getStatusSummary', function () { + it('should return initial summary with zero counts', function () { const summary = registry.getStatusSummary(); assert.equal(summary[COMPONENT_STATUS_LEVELS.HEALTHY], 0); @@ -277,7 +280,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(summary[COMPONENT_STATUS_LEVELS.UNKNOWN], 0); }); - it('should count components by status', () => { + it('should count components by status', function () { registry.setStatus('comp1', 'healthy'); registry.setStatus('comp2', 'healthy'); registry.setStatus('comp3', 'error'); @@ -296,8 +299,8 @@ describe('ComponentStatusRegistry', () => { }); // Test aggregate functionality through getAggregatedFromAllThreads - describe('aggregation functionality (via getAggregatedFromAllThreads)', () => { - it('should aggregate single component from multiple threads', () => { + describe('aggregation functionality (via getAggregatedFromAllThreads)', function () { + it('should aggregate single component from multiple threads', function () { const allStatuses = new Map([ [ 'myComponent@main', @@ -338,7 +341,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(aggStatus.abnormalities, undefined); // All healthy, no abnormalities }); - it('should detect abnormalities when statuses differ', () => { + it('should detect abnormalities when statuses differ', function () { const allStatuses = new Map([ [ 'database@worker-0', @@ -381,7 +384,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(worker0Abnormality.workerIndex, -1); // No workerIndex in input }); - it('should prioritize non-healthy messages', () => { + it('should prioritize non-healthy messages', function () { const allStatuses = new Map([ [ 'api@worker-0', @@ -416,7 +419,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(aggStatus.latestMessage, 'High latency detected'); // Non-healthy message preferred }); - it('should handle status priority correctly', () => { + it('should handle status priority correctly', function () { const testCases = [ { statuses: ['healthy', 'healthy', 'healthy'], expected: 'healthy' }, { statuses: ['healthy', 'unknown', 'healthy'], expected: 'unknown' }, @@ -440,7 +443,7 @@ describe('ComponentStatusRegistry', () => { } }); - it('should handle worker index in status data', () => { + it('should handle worker index in status data', function () { const allStatuses = new Map([ [ 'service@worker-1', @@ -470,7 +473,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(abnormality.workerIndex, 2); }); - it('should handle main thread correctly', () => { + it('should handle main thread correctly', function () { const allStatuses = new Map([ [ 'logger@main', @@ -498,8 +501,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('static getAggregatedFromAllThreads method', () => { - it('should collect and aggregate statuses', async () => { + describe('static getAggregatedFromAllThreads method', function () { + it('should collect and aggregate statuses', async function () { // Mock the crossThreadCollector to avoid actual ITC communication const { crossThreadCollector } = require('#src/components/status/crossThread'); const originalCollect = crossThreadCollector.collect; @@ -540,9 +543,9 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('getAggregatedStatusFor method', () => { - describe('basic aggregation scenarios', () => { - it('should return status for exact component match only', async () => { + describe('getAggregatedStatusFor method', function () { + describe('basic aggregation scenarios', function () { + it('should return status for exact component match only', async function () { const consolidatedStatuses = new Map([ [ 'application-template', @@ -563,7 +566,7 @@ describe('ComponentStatusRegistry', () => { assert.deepEqual(result.lastChecked, { workers: { 0: 1000 } }); }); - it('should return aggregated status for sub-components only', async () => { + it('should return aggregated status for sub-components only', async function () { const consolidatedStatuses = new Map([ [ 'application-template.rest', @@ -593,7 +596,7 @@ describe('ComponentStatusRegistry', () => { assert.deepEqual(result.lastChecked, { workers: { 0: 1000 } }); }); - it('should combine exact match with sub-components', async () => { + it('should combine exact match with sub-components', async function () { const consolidatedStatuses = new Map([ [ 'application-template', @@ -623,7 +626,7 @@ describe('ComponentStatusRegistry', () => { assert.deepEqual(result.lastChecked, { workers: { 0: 1000 } }); }); - it('should return unknown status when component not found', async () => { + it('should return unknown status when component not found', async function () { const consolidatedStatuses = new Map([ [ 'other-component', @@ -644,8 +647,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('status priority and aggregation logic', () => { - it('should prioritize error over other statuses', async () => { + describe('status priority and aggregation logic', function () { + it('should prioritize error over other statuses', async function () { const consolidatedStatuses = new Map([ [ 'app.component1', @@ -684,7 +687,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(result.details['app.component2'].status, COMPONENT_STATUS_LEVELS.ERROR); }); - it('should prioritize loading over healthy statuses', async () => { + it('should prioritize loading over healthy statuses', async function () { const consolidatedStatuses = new Map([ [ 'service.api', @@ -714,7 +717,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(result.details['service.database'].status, COMPONENT_STATUS_LEVELS.LOADING); }); - it('should return healthy when all components healthy', async () => { + it('should return healthy when all components healthy', async function () { const consolidatedStatuses = new Map([ [ 'webapp.frontend', @@ -743,7 +746,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(result.details, undefined); }); - it('should handle mixed status scenarios correctly', async () => { + it('should handle mixed status scenarios correctly', async function () { const consolidatedStatuses = new Map([ [ 'mixed', @@ -800,8 +803,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('details and message generation', () => { - it('should include details when components have issues', async () => { + describe('details and message generation', function () { + it('should include details when components have issues', async function () { const consolidatedStatuses = new Map([ [ 'app.good', @@ -832,7 +835,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(result.details['app.good'], undefined); // Healthy components not included }); - it('should not include details when all components healthy', async () => { + it('should not include details when all components healthy', async function () { const consolidatedStatuses = new Map([ [ 'service.web', @@ -860,7 +863,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(result.message, 'All components loaded successfully'); }); - it('should generate descriptive messages for problem components', async () => { + it('should generate descriptive messages for problem components', async function () { const consolidatedStatuses = new Map([ [ 'system.auth', @@ -889,7 +892,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(result.message, expectedMessage); }); - it('should format component keys correctly in messages', async () => { + it('should format component keys correctly in messages', async function () { const consolidatedStatuses = new Map([ [ 'my-app.long-component-name', @@ -908,8 +911,8 @@ describe('ComponentStatusRegistry', () => { }); }); - describe('edge cases and error handling', () => { - it('should handle empty consolidated statuses gracefully', async () => { + describe('edge cases and error handling', function () { + it('should handle empty consolidated statuses gracefully', async function () { const consolidatedStatuses = new Map(); const result = await registry.getAggregatedStatusFor('any-component', consolidatedStatuses); @@ -919,7 +922,7 @@ describe('ComponentStatusRegistry', () => { assert.deepEqual(result.lastChecked, { workers: {} }); }); - it('should handle null/undefined consolidated statuses', async () => { + it('should handle null/undefined consolidated statuses', async function () { // Mock the static method to avoid cross-thread communication in tests const originalMethod = ComponentStatusRegistry.getAggregatedFromAllThreads; ComponentStatusRegistry.getAggregatedFromAllThreads = async () => new Map(); @@ -938,7 +941,7 @@ describe('ComponentStatusRegistry', () => { } }); - it('should work with pre-provided consolidated statuses', async () => { + it('should work with pre-provided consolidated statuses', async function () { const consolidatedStatuses = new Map([ [ 'provided.test', @@ -957,7 +960,7 @@ describe('ComponentStatusRegistry', () => { assert.equal(result.message, 'All components loaded successfully'); }); - it('should fetch consolidated statuses when not provided', async () => { + it('should fetch consolidated statuses when not provided', async function () { // This test ensures the method works without pre-provided statuses // It will attempt to fetch from ComponentStatusRegistry.getAggregatedFromAllThreads registry.setStatus('test-fetch', COMPONENT_STATUS_LEVELS.HEALTHY, 'Local component'); @@ -989,7 +992,7 @@ describe('ComponentStatusRegistry', () => { } }); - it('should handle missing latestMessage gracefully', async () => { + it('should handle missing latestMessage gracefully', async function () { const consolidatedStatuses = new Map([ [ 'no-msg.component', diff --git a/unitTests/components/status/api.test.js b/unitTests/components/status/api.test.js index 995e29642..b3588a949 100644 --- a/unitTests/components/status/api.test.js +++ b/unitTests/components/status/api.test.js @@ -1,12 +1,15 @@ -const { describe, it, afterEach } = require('mocha'); const assert = require('node:assert/strict'); -const { statusForComponent, lifecycle, reset, STATUS, internal } = require('#src/components/status/index'); +const { statusForComponent, lifecycle, reset, STATUS } = require('#src/components/status/index'); +const { internal } = require('#src/components/status/index'); -describe('Component Status Public API', () => { - afterEach(reset); +describe('Component Status Public API', function () { + afterEach(function () { + // Clean up after each test + reset(); + }); - describe('statusForComponent() API', () => { - it('should provide fluent interface for status reporting', () => { + describe('statusForComponent() API', function () { + it('should provide fluent interface for status reporting', function () { // Test chaining const result = statusForComponent('test-service') .healthy('Service initialized') @@ -22,7 +25,7 @@ describe('Component Status Public API', () => { assert.equal(status.message, 'Connection lost'); }); - it('should report healthy status', () => { + it('should report healthy status', function () { statusForComponent('api').healthy('API server running'); const status = statusForComponent('api').get(); @@ -30,7 +33,7 @@ describe('Component Status Public API', () => { assert.equal(status.message, 'API server running'); }); - it('should report healthy status without message', () => { + it('should report healthy status without message', function () { statusForComponent('cache').healthy(); const status = statusForComponent('cache').get(); @@ -38,7 +41,7 @@ describe('Component Status Public API', () => { assert.equal(status.message, undefined); }); - it('should report warning status', () => { + it('should report warning status', function () { statusForComponent('database').warning('Replication lag detected'); const status = statusForComponent('database').get(); @@ -46,7 +49,7 @@ describe('Component Status Public API', () => { assert.equal(status.message, 'Replication lag detected'); }); - it('should report error status with Error object', () => { + it('should report error status with Error object', function () { const error = new Error('Connection timeout'); statusForComponent('redis').error('Redis connection failed', error); @@ -56,7 +59,7 @@ describe('Component Status Public API', () => { assert.equal(status.error, error); }); - it('should report loading status', () => { + it('should report loading status', function () { statusForComponent('ml-model').loading('Loading model weights'); const status = statusForComponent('ml-model').get(); @@ -64,7 +67,7 @@ describe('Component Status Public API', () => { assert.equal(status.message, 'Loading model weights'); }); - it('should report loading status with default message', () => { + it('should report loading status with default message', function () { statusForComponent('data-processor').loading(); const status = statusForComponent('data-processor').get(); @@ -72,7 +75,7 @@ describe('Component Status Public API', () => { assert.equal(status.message, 'Loading...'); }); - it('should report unknown status', () => { + it('should report unknown status', function () { statusForComponent('mystery').unknown('State unclear'); const status = statusForComponent('mystery').get(); @@ -80,21 +83,21 @@ describe('Component Status Public API', () => { assert.equal(status.message, 'State unclear'); }); - it('should reuse builder instances', () => { + it('should reuse builder instances', function () { const builder1 = statusForComponent('shared'); const builder2 = statusForComponent('shared'); assert.strictEqual(builder1, builder2); }); - it('should return undefined for non-existent component', () => { + it('should return undefined for non-existent component', function () { const status = statusForComponent('non-existent').get(); assert.equal(status, undefined); }); }); - describe('lifecycle API', () => { - it('should handle component loading lifecycle', () => { + describe('lifecycle API', function () { + it('should handle component loading lifecycle', function () { // Loading phase lifecycle.loading('auth-service', 'Initializing authentication'); let status = internal.query.get('auth-service'); @@ -108,7 +111,7 @@ describe('Component Status Public API', () => { assert.equal(status.message, 'Authentication ready'); }); - it('should handle component failure', () => { + it('should handle component failure', function () { lifecycle.loading('payment-gateway'); const error = new Error('Invalid API key'); @@ -121,8 +124,8 @@ describe('Component Status Public API', () => { }); }); - describe('reset API', () => { - it('should clear all component statuses', () => { + describe('reset API', function () { + it('should clear all component statuses', function () { statusForComponent('temp1').healthy(); statusForComponent('temp2').error('Failed'); @@ -136,8 +139,8 @@ describe('Component Status Public API', () => { }); }); - describe('STATUS constants', () => { - it('should expose status level constants', () => { + describe('STATUS constants', function () { + it('should expose status level constants', function () { assert.equal(STATUS.HEALTHY, 'healthy'); assert.equal(STATUS.WARNING, 'warning'); assert.equal(STATUS.ERROR, 'error'); diff --git a/unitTests/components/status/crossThread.test.js b/unitTests/components/status/crossThread.test.js index 055099fda..4d7876444 100644 --- a/unitTests/components/status/crossThread.test.js +++ b/unitTests/components/status/crossThread.test.js @@ -1,28 +1,27 @@ -const { describe, it, beforeEach, afterEach } = require('mocha'); const assert = require('node:assert/strict'); const sinon = require('sinon'); const { CrossThreadStatusCollector, StatusAggregator } = require('#src/components/status/crossThread'); const { ComponentStatusRegistry } = require('#src/components/status/ComponentStatusRegistry'); -const itcModule = require('#src/server/threads/itc'); +const itcModule = require('#js/server/threads/itc'); const manageThreadsModule = require('#js/server/threads/manageThreads'); -describe('CrossThread Module #skip-typestrip', () => { +describe('CrossThread Module', function () { let sendItcEventStub; let onMessageByTypeStub; let getWorkerIndexStub; - beforeEach(() => { + beforeEach(function () { // Stub ITC functions sendItcEventStub = sinon.stub(itcModule, 'sendItcEvent').resolves(); onMessageByTypeStub = sinon.stub(manageThreadsModule, 'onMessageByType'); getWorkerIndexStub = sinon.stub(manageThreadsModule, 'getWorkerIndex').returns(0); }); - afterEach(() => { + afterEach(function () { sinon.restore(); }); - describe('CrossThreadStatusCollector', () => { + describe('CrossThreadStatusCollector', function () { let collector; let registry; @@ -31,12 +30,12 @@ describe('CrossThread Module #skip-typestrip', () => { registry = new ComponentStatusRegistry(); }); - afterEach(() => { + afterEach(function () { collector.cleanup(); registry.reset(); }); - it('should collect status from local thread only when no responses', async () => { + it('should collect status from local thread only when no responses', async function () { registry.setStatus('localComp', 'healthy', 'All good'); // Test with main thread (undefined) getWorkerIndexStub.returns(undefined); @@ -57,7 +56,8 @@ describe('CrossThread Module #skip-typestrip', () => { getWorkerCountStub.restore(); }); - it('should collect status from multiple threads', async () => { + it('should collect status from multiple threads', async function () { + this.timeout(5000); // Allow enough time for async operations registry.setStatus('sharedComp', 'healthy', 'Local is healthy'); // Test with main thread (undefined) getWorkerIndexStub.returns(undefined); @@ -117,9 +117,9 @@ describe('CrossThread Module #skip-typestrip', () => { // Cleanup getWorkerCountStub.restore(); - }).timeout(5000); + }); - it('should handle ITC send failure', async () => { + it('should handle ITC send failure', async function () { registry.setStatus('fallbackComp', 'error', 'Local error'); // Test with main thread (undefined) getWorkerIndexStub.returns(undefined); @@ -142,7 +142,8 @@ describe('CrossThread Module #skip-typestrip', () => { getWorkerCountStub.restore(); }); - it('should handle collection timeout', async () => { + it('should handle collection timeout', async function () { + this.timeout(5000); const shortTimeoutCollector = new CrossThreadStatusCollector(50); // Very short timeout registry.setStatus('timeoutComp', 'loading', 'Loading...'); // Test with main thread (undefined) @@ -162,9 +163,10 @@ describe('CrossThread Module #skip-typestrip', () => { shortTimeoutCollector.cleanup(); getWorkerCountStub.restore(); - }).timeout(5000); + }); - it('should complete early when all threads respond', async () => { + it('should complete early when all threads respond', async function () { + this.timeout(5000); registry.setStatus('fastComp', 'healthy', 'Main thread'); getWorkerIndexStub.returns(0); @@ -211,9 +213,9 @@ describe('CrossThread Module #skip-typestrip', () => { // Cleanup the stub getWorkerCountStub.restore(); - }).timeout(5000); + }); - it('should reuse listener across multiple collections', async () => { + it('should reuse listener across multiple collections', async function () { // First collection await collector.collect(registry); assert.equal(onMessageByTypeStub.callCount, 1); @@ -223,7 +225,7 @@ describe('CrossThread Module #skip-typestrip', () => { assert.equal(onMessageByTypeStub.callCount, 1); }); - it('should properly clean up resources', () => { + it('should properly clean up resources', function () { // Set up some pending requests collector['awaitingResponses'].set(1, []); collector['awaitingResponses'].set(2, []); @@ -240,10 +242,10 @@ describe('CrossThread Module #skip-typestrip', () => { }); }); - describe('StatusAggregator', () => { + describe('StatusAggregator', function () { let clock; - beforeEach(() => { + beforeEach(function () { clock = sinon.useFakeTimers(); }); @@ -251,7 +253,7 @@ describe('CrossThread Module #skip-typestrip', () => { clock.restore(); }); - it('should aggregate single component from single thread', () => { + it('should aggregate single component from single thread', function () { const allStatuses = new Map([ [ 'database@worker-0', @@ -275,7 +277,7 @@ describe('CrossThread Module #skip-typestrip', () => { assert.equal(aggStatus.abnormalities, undefined); }); - it('should detect abnormalities when statuses differ', () => { + it('should detect abnormalities when statuses differ', function () { const allStatuses = new Map([ [ 'api@worker-0', @@ -318,7 +320,7 @@ describe('CrossThread Module #skip-typestrip', () => { assert.equal(worker0Abnormality.workerIndex, -1); // No workerIndex in input }); - it('should prioritize non-healthy messages', () => { + it('should prioritize non-healthy messages', function () { const allStatuses = new Map([ [ 'service@worker-0', @@ -353,7 +355,7 @@ describe('CrossThread Module #skip-typestrip', () => { assert.equal(aggStatus.latestMessage, 'High latency detected'); // Non-healthy message preferred }); - it('should handle status priority correctly', () => { + it('should handle status priority correctly', function () { const testCases = [ { statuses: ['healthy', 'healthy', 'healthy'], expected: 'healthy' }, { statuses: ['healthy', 'unknown', 'healthy'], expected: 'unknown' }, @@ -377,7 +379,7 @@ describe('CrossThread Module #skip-typestrip', () => { } }); - it('should handle main thread correctly', () => { + it('should handle main thread correctly', function () { const allStatuses = new Map([ [ 'logger@main', @@ -404,7 +406,7 @@ describe('CrossThread Module #skip-typestrip', () => { assert.equal(aggStatus.lastChecked.workers[1], 2000); }); - it('should handle worker index in status data', () => { + it('should handle worker index in status data', function () { const allStatuses = new Map([ [ 'cache@worker-1', diff --git a/unitTests/components/status/errors.test.js b/unitTests/components/status/errors.test.js index 81019941a..2b0f0b518 100644 --- a/unitTests/components/status/errors.test.js +++ b/unitTests/components/status/errors.test.js @@ -1,4 +1,3 @@ -const { describe, it } = require('mocha'); const assert = require('node:assert/strict'); const { ComponentStatusError, @@ -10,9 +9,9 @@ const { } = require('#src/components/status/errors'); const { HTTP_STATUS_CODES } = require('#js/utility/errors/commonErrors'); -describe('Component Status Errors', () => { - describe('ComponentStatusError', () => { - it('should create base error with default status code', () => { +describe('Component Status Errors', function () { + describe('ComponentStatusError', function () { + it('should create base error with default status code', function () { const error = new ComponentStatusError('Test error'); assert.equal(error.name, 'ComponentStatusError'); @@ -22,15 +21,15 @@ describe('Component Status Errors', () => { assert.ok(error.stack); }); - it('should create base error with custom status code', () => { + it('should create base error with custom status code', function () { const error = new ComponentStatusError('Bad request', HTTP_STATUS_CODES.BAD_REQUEST); assert.equal(error.statusCode, HTTP_STATUS_CODES.BAD_REQUEST); }); }); - describe('CrossThreadTimeoutError', () => { - it('should create timeout error with details', () => { + describe('CrossThreadTimeoutError', function () { + it('should create timeout error with details', function () { const error = new CrossThreadTimeoutError(123, 5000, 3); assert.equal(error.name, 'CrossThreadTimeoutError'); @@ -44,8 +43,8 @@ describe('Component Status Errors', () => { }); }); - describe('ITCError', () => { - it('should create ITC error without cause', () => { + describe('ITCError', function () { + it('should create ITC error without cause', function () { const error = new ITCError('sendEvent'); assert.equal(error.name, 'ITCError'); @@ -55,7 +54,7 @@ describe('Component Status Errors', () => { assert.ok(error.message.includes('Unknown error')); }); - it('should create ITC error with cause', () => { + it('should create ITC error with cause', function () { const cause = new Error('Network failure'); const error = new ITCError('broadcastStatus', cause); @@ -64,8 +63,8 @@ describe('Component Status Errors', () => { }); }); - describe('AggregationError', () => { - it('should create aggregation error', () => { + describe('AggregationError', function () { + it('should create aggregation error', function () { const cause = new Error('Invalid data'); const error = new AggregationError(10, cause); @@ -77,8 +76,8 @@ describe('Component Status Errors', () => { }); }); - describe('ComponentStatusOperationError', () => { - it('should create operation error', () => { + describe('ComponentStatusOperationError', function () { + it('should create operation error', function () { const error = new ComponentStatusOperationError('my-component', 'setStatus', 'Invalid status level'); assert.equal(error.name, 'ComponentStatusOperationError'); @@ -90,8 +89,8 @@ describe('Component Status Errors', () => { }); }); - describe('CrossThreadCollectionError', () => { - it('should create collection error for partial success', () => { + describe('CrossThreadCollectionError', function () { + it('should create collection error for partial success', function () { const result = { success: true, collectedFromThreads: 5, @@ -110,7 +109,7 @@ describe('Component Status Errors', () => { assert.ok(error.message.includes('3 timed out')); }); - it('should create collection error for complete failure', () => { + it('should create collection error for complete failure', function () { const result = { success: false, collectedFromThreads: 0, @@ -125,7 +124,7 @@ describe('Component Status Errors', () => { assert.ok(error.message.includes('Timeout')); }); - it('should provide detailed diagnostics', () => { + it('should provide detailed diagnostics', function () { const result = { success: true, collectedFromThreads: 3, diff --git a/unitTests/components/status/index.test.js b/unitTests/components/status/index.test.js index 526e8c36a..a362efb23 100644 --- a/unitTests/components/status/index.test.js +++ b/unitTests/components/status/index.test.js @@ -1,15 +1,20 @@ -const { describe, it, beforeEach, after } = require('mocha'); const assert = require('node:assert/strict'); const { statusForComponent, reset, STATUS, internal } = require('#src/components/status/index'); const { ComponentStatus } = internal; -describe('Component Status API', () => { - beforeEach(reset); +describe('Component Status API', function () { + beforeEach(function () { + // Clear the registry before each test + reset(); + }); - after(reset); + after(function () { + // Clean up the registry after all tests complete + reset(); + }); describe('statusForComponent', function () { - it('should return a ComponentStatusBuilder instance', () => { + it('should return a ComponentStatusBuilder instance', function () { const api = statusForComponent('test-component'); assert.ok(api); assert.equal(typeof api.warning, 'function'); @@ -20,28 +25,28 @@ describe('Component Status API', () => { assert.equal(typeof api.get, 'function'); }); - it('should return the same instance for the same component name', () => { + it('should return the same instance for the same component name', function () { const api1 = statusForComponent('cached-component'); const api2 = statusForComponent('cached-component'); assert.strictEqual(api1, api2); }); - it('should return different instances for different component names', () => { + it('should return different instances for different component names', function () { const api1 = statusForComponent('component-1'); const api2 = statusForComponent('component-2'); assert.notStrictEqual(api1, api2); }); }); - describe('ComponentStatusAPI methods', () => { + describe('ComponentStatusAPI methods', function () { let api; - beforeEach(() => { + beforeEach(function () { api = statusForComponent('test-api-component'); }); describe('warning', function () { - it('should set component status to warning', () => { + it('should set component status to warning', function () { api.warning('High memory usage detected'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -52,7 +57,7 @@ describe('Component Status API', () => { }); describe('error', function () { - it('should set component status to error with message only', () => { + it('should set component status to error with message only', function () { api.error('Connection failed'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -62,7 +67,7 @@ describe('Component Status API', () => { assert.equal(status.error, undefined); }); - it('should set component status to error with message and Error object', () => { + it('should set component status to error with message and Error object', function () { const error = new Error('Database timeout'); api.error('Failed to connect to database', error); @@ -75,7 +80,7 @@ describe('Component Status API', () => { }); describe('healthy', function () { - it('should set component status to healthy with message', () => { + it('should set component status to healthy with message', function () { api.healthy('Component is running smoothly'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -84,7 +89,7 @@ describe('Component Status API', () => { assert.equal(status.message, 'Component is running smoothly'); }); - it('should set component status to healthy without message', () => { + it('should set component status to healthy without message', function () { api.healthy(); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -95,7 +100,7 @@ describe('Component Status API', () => { }); describe('loading', function () { - it('should set component status to loading with message', () => { + it('should set component status to loading with message', function () { api.loading('Initializing database connection'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -104,7 +109,7 @@ describe('Component Status API', () => { assert.equal(status.message, 'Initializing database connection'); }); - it('should set component status to loading without message', () => { + it('should set component status to loading without message', function () { api.loading(); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -115,7 +120,7 @@ describe('Component Status API', () => { }); describe('unknown', function () { - it('should set component status to unknown with message', () => { + it('should set component status to unknown with message', function () { api.unknown('Component state is unclear'); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -124,7 +129,7 @@ describe('Component Status API', () => { assert.equal(status.message, 'Component state is unclear'); }); - it('should set component status to unknown without message', () => { + it('should set component status to unknown without message', function () { api.unknown(); const status = internal.componentStatusRegistry.getStatus('test-api-component'); @@ -135,12 +140,12 @@ describe('Component Status API', () => { }); describe('get', function () { - it('should return undefined for non-existent component', () => { + it('should return undefined for non-existent component', function () { const status = api.get(); assert.equal(status, undefined); }); - it('should return the current status of the component', () => { + it('should return the current status of the component', function () { api.healthy('Running'); const status = api.get(); @@ -149,7 +154,7 @@ describe('Component Status API', () => { assert.equal(status.message, 'Running'); }); - it('should reflect status changes', () => { + it('should reflect status changes', function () { // Initially healthy api.healthy('Started'); let status = api.get(); @@ -169,7 +174,7 @@ describe('Component Status API', () => { }); describe('Integration with componentStatusRegistry', function () { - it('should properly integrate with the global registry', () => { + it('should properly integrate with the global registry', function () { const api1 = statusForComponent('integration-test-1'); const api2 = statusForComponent('integration-test-2'); @@ -188,7 +193,7 @@ describe('Component Status API', () => { assert.equal(api2.get().status, STATUS.WARNING); }); - it('should work with registry methods', () => { + it('should work with registry methods', function () { const api = statusForComponent('registry-integration'); // Set status via API @@ -207,7 +212,7 @@ describe('Component Status API', () => { }); describe('Multiple component workflow', function () { - it('should handle multiple components independently', () => { + it('should handle multiple components independently', function () { const authApi = statusForComponent('auth-service'); const dbApi = statusForComponent('database'); const cacheApi = statusForComponent('cache'); @@ -233,7 +238,7 @@ describe('Component Status API', () => { }); describe('Status transition scenarios', function () { - it('should handle component lifecycle transitions', () => { + it('should handle component lifecycle transitions', function () { const api = statusForComponent('lifecycle-component'); // Component starts loading diff --git a/unitTests/components/status/registry.test.js b/unitTests/components/status/registry.test.js index b862c5eff..1d9ac940e 100644 --- a/unitTests/components/status/registry.test.js +++ b/unitTests/components/status/registry.test.js @@ -1,24 +1,23 @@ -const { describe, it, after } = require('mocha'); const assert = require('node:assert/strict'); const { ComponentStatusRegistry } = require('#src/components/status/ComponentStatusRegistry'); const { componentStatusRegistry } = require('#src/components/status/registry'); -describe('componentStatusRegistry singleton', () => { - after(() => { +describe('componentStatusRegistry singleton', function () { + after(function () { // Clean up the global singleton after tests componentStatusRegistry.reset(); }); - it('should export a ComponentStatusRegistry instance', () => { + it('should export a ComponentStatusRegistry instance', function () { assert.ok(componentStatusRegistry instanceof ComponentStatusRegistry); }); - it('should be a singleton instance', () => { + it('should be a singleton instance', function () { const { componentStatusRegistry: registry2 } = require('#src/components/status/registry'); assert.strictEqual(componentStatusRegistry, registry2); }); - it('should have all ComponentStatusRegistry methods', () => { + it('should have all ComponentStatusRegistry methods', function () { // Check core methods exist assert.equal(typeof componentStatusRegistry.reset, 'function'); assert.equal(typeof componentStatusRegistry.setStatus, 'function'); @@ -34,7 +33,7 @@ describe('componentStatusRegistry singleton', () => { assert.equal(typeof componentStatusRegistry.getStatusSummary, 'function'); }); - it('should work with basic operations', () => { + it('should work with basic operations', function () { // Clean up any existing state componentStatusRegistry.reset();