From f389cc99430903ac1bf7c89391acd4d9956b2017 Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 17:12:57 -0700 Subject: [PATCH 01/10] Update test coverage for filesystem access. --- test/unitTests/shared/util.tests.ts | 219 ++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/test/unitTests/shared/util.tests.ts b/test/unitTests/shared/util.tests.ts index 266bd617b..67f3c1c92 100644 --- a/test/unitTests/shared/util.tests.ts +++ b/test/unitTests/shared/util.tests.ts @@ -1,7 +1,11 @@ import * as assert from "assert"; import * as sinon from "sinon"; +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; import { Util } from "../../../src/shared/util"; +import * as fileSystemHelper from "../../../src/shared/util/fileSystemHelper"; describe("Library/Util", () => { let sandbox: sinon.SinonSandbox; @@ -108,4 +112,219 @@ describe("Library/Util", () => { test(-1, "00:00:00.000", "invalid input"); }); }); + + describe("Library/Util/fileSystemHelper", () => { + let sandbox: sinon.SinonSandbox; + const tempDir = path.join(os.tmpdir(), "appinsights-test-" + Date.now()); + const tempFilePath = path.join(tempDir, "test-file.txt"); + const testContent = "Hello, world!"; + + before(async () => { + // Ensure test directory exists + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); + } + }); + + after(async () => { + // Clean up test directory + if (fs.existsSync(tempDir)) { + // Try to remove all files in the directory + try { + const files = fs.readdirSync(tempDir); + for (const file of files) { + fs.unlinkSync(path.join(tempDir, file)); + } + fs.rmdirSync(tempDir); + } catch (e) { + console.error("Error cleaning up test directory:", e); + } + } + }); + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe("#confirmDirExists()", () => { + it("should do nothing if directory already exists", async () => { + await fileSystemHelper.confirmDirExists(tempDir); + assert.ok(fs.existsSync(tempDir), "Directory should exist"); + }); + + it("should create directory if it does not exist", async () => { + const newDir = path.join(tempDir, "new-dir"); + await fileSystemHelper.confirmDirExists(newDir); + assert.ok(fs.existsSync(newDir), "Directory should be created"); + }); + }); + + describe("#getShallowDirectorySize()", () => { + it("should compute the total size of all files in a directory", async () => { + // Create test files + const file1 = path.join(tempDir, "file1.txt"); + const file2 = path.join(tempDir, "file2.txt"); + fs.writeFileSync(file1, "content1"); + fs.writeFileSync(file2, "content2content2"); + + const size = await fileSystemHelper.getShallowDirectorySize(tempDir); + assert.ok(size >= "content1".length + "content2content2".length, "Size should include all files"); + }); + + it("should only count files at root level and ignore subdirectories", async () => { + // Create test files and subdirectory + const file1 = path.join(tempDir, "file1-root.txt"); + const subDir = path.join(tempDir, "subdir"); + if (!fs.existsSync(subDir)) { + fs.mkdirSync(subDir, { recursive: true }); + } + const file2 = path.join(subDir, "file2-subdir.txt"); + + fs.writeFileSync(file1, "content1-root"); + fs.writeFileSync(file2, "content2-subdir-content2"); + + const size = await fileSystemHelper.getShallowDirectorySize(tempDir); + assert.ok(size === "content1-root".length || size > "content1-root".length, + "Size should only include root files"); + }); + + it("should handle empty directories", async () => { + // Create empty directory + const emptyDir = path.join(tempDir, "empty-dir"); + if (!fs.existsSync(emptyDir)) { + fs.mkdirSync(emptyDir, { recursive: true }); + } + + const size = await fileSystemHelper.getShallowDirectorySize(emptyDir); + assert.strictEqual(size, 0); + }); + }); + + describe("#getShallowDirectorySizeSync()", () => { + it("should compute the total size of all files in a directory synchronously", () => { + // Create test files + const file1 = path.join(tempDir, "file1-sync.txt"); + const file2 = path.join(tempDir, "file2-sync.txt"); + fs.writeFileSync(file1, "content1-sync"); + fs.writeFileSync(file2, "content2-sync-content2"); + + const size = fileSystemHelper.getShallowDirectorySizeSync(tempDir); + assert.ok(size >= "content1-sync".length + "content2-sync-content2".length, + "Size should include all files"); + }); + + it("should handle empty directories", () => { + // Create empty directory + const emptyDir = path.join(tempDir, "empty-dir-sync"); + if (!fs.existsSync(emptyDir)) { + fs.mkdirSync(emptyDir, { recursive: true }); + } + + const size = fileSystemHelper.getShallowDirectorySizeSync(emptyDir); + assert.strictEqual(size, 0); + }); + }); + + describe("#getShallowFileSize()", () => { + it("should return the size of a file", async () => { + // Create test file + fs.writeFileSync(tempFilePath, testContent); + + const size = await fileSystemHelper.getShallowFileSize(tempFilePath); + assert.strictEqual(size, testContent.length); + }); + + it("should not return a size for a directory", async () => { + const result = await fileSystemHelper.getShallowFileSize(tempDir); + assert.strictEqual(result, undefined); + }); + }); + + describe("promisified fs functions", () => { + it("should promisify fs.stat correctly", async () => { + // Create test file + fs.writeFileSync(tempFilePath, testContent); + + const stats = await fileSystemHelper.statAsync(tempFilePath); + assert.ok(stats.isFile(), "Should be a file"); + assert.strictEqual(stats.size, testContent.length); + }); + + it("should promisify fs.lstat correctly", async () => { + // Create test file + fs.writeFileSync(tempFilePath, testContent); + + const stats = await fileSystemHelper.lstatAsync(tempFilePath); + assert.ok(stats.isFile(), "Should be a file"); + assert.strictEqual(stats.size, testContent.length); + }); + + it("should promisify fs.mkdir correctly", async () => { + const newDir = path.join(tempDir, "mkdir-test"); + await fileSystemHelper.mkdirAsync(newDir); + assert.ok(fs.existsSync(newDir), "Directory should be created"); + }); + + it("should promisify fs.access correctly", async () => { + // Create test file + fs.writeFileSync(tempFilePath, testContent); + + await fileSystemHelper.accessAsync(tempFilePath); + // If it doesn't throw, the test passes + assert.ok(true); + }); + + it("should promisify fs.appendFile correctly", async () => { + // Create test file + fs.writeFileSync(tempFilePath, testContent); + + const additionalContent = " More content!"; + await fileSystemHelper.appendFileAsync(tempFilePath, additionalContent); + const result = fs.readFileSync(tempFilePath, 'utf8'); + assert.strictEqual(result, testContent + additionalContent); + }); + + it("should promisify fs.writeFile correctly", async () => { + const newContent = "New content!"; + await fileSystemHelper.writeFileAsync(tempFilePath, newContent); + const result = fs.readFileSync(tempFilePath, 'utf8'); + assert.strictEqual(result, newContent); + }); + + it("should promisify fs.readFile correctly", async () => { + // Create test file with known content + const fileContent = "Read file test content"; + fs.writeFileSync(tempFilePath, fileContent); + + const result = await fileSystemHelper.readFileAsync(tempFilePath, 'utf8'); + assert.strictEqual(result.toString(), fileContent); + }); + + it("should promisify fs.readdir correctly", async () => { + // Create test files + const file1 = path.join(tempDir, "file1-readdir.txt"); + const file2 = path.join(tempDir, "file2-readdir.txt"); + fs.writeFileSync(file1, "content"); + fs.writeFileSync(file2, "content"); + + const files = await fileSystemHelper.readdirAsync(tempDir); + assert.ok(files.some(f => f === "file1-readdir.txt"), "Should include file1"); + assert.ok(files.some(f => f === "file2-readdir.txt"), "Should include file2"); + }); + + it("should promisify fs.unlink correctly", async () => { + // Create test file to be deleted + const fileToDelete = path.join(tempDir, "delete-me.txt"); + fs.writeFileSync(fileToDelete, "delete me"); + assert.ok(fs.existsSync(fileToDelete), "File should exist before deletion"); + + await fileSystemHelper.unlinkAsync(fileToDelete); + assert.ok(!fs.existsSync(fileToDelete), "File should be deleted"); + }); + }); + }); }); From 745936caa3fec29b94d7a366ce6ec13dadd66f14 Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 17:37:21 -0700 Subject: [PATCH 02/10] Add coverage for ETW tests. --- src/agent/diagnostics/etwDiagnosticLogger.ts | 4 +- .../writers/{etwWritter.ts => etwWriter.ts} | 2 +- .../diagnostics/diagnosticLogger.tests.ts | 142 ++++++++++++++++++ 3 files changed, 145 insertions(+), 3 deletions(-) rename src/agent/diagnostics/writers/{etwWritter.ts => etwWriter.ts} (97%) diff --git a/src/agent/diagnostics/etwDiagnosticLogger.ts b/src/agent/diagnostics/etwDiagnosticLogger.ts index a42ba43f9..eee705746 100644 --- a/src/agent/diagnostics/etwDiagnosticLogger.ts +++ b/src/agent/diagnostics/etwDiagnosticLogger.ts @@ -3,14 +3,14 @@ import { IDiagnosticLog } from "../types"; import { BaseDiagnosticLogger } from "./baseDiagnosticLogger"; -import { EtwWritter } from "./writers/etwWritter"; +import { EtwWriter } from "./writers/etwWriter"; export class EtwDiagnosticLogger extends BaseDiagnosticLogger { constructor(instrumentationKey: string) { super(instrumentationKey); - this._agentLogger = new EtwWritter(); + this._agentLogger = new EtwWriter(); } public logMessage(diagnosticLog: IDiagnosticLog) { diff --git a/src/agent/diagnostics/writers/etwWritter.ts b/src/agent/diagnostics/writers/etwWriter.ts similarity index 97% rename from src/agent/diagnostics/writers/etwWritter.ts rename to src/agent/diagnostics/writers/etwWriter.ts index f5e4134e1..e7bb592d2 100644 --- a/src/agent/diagnostics/writers/etwWritter.ts +++ b/src/agent/diagnostics/writers/etwWriter.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import { Util } from "../../../shared/util"; import { IAgentLogger } from "../../types"; -export class EtwWritter implements IAgentLogger { +export class EtwWriter implements IAgentLogger { private _etwModule: any; constructor() { diff --git a/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts b/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts index 659b4a653..795319c18 100644 --- a/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts +++ b/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts @@ -4,6 +4,8 @@ import * as assert from "assert"; import * as sinon from "sinon"; import { DiagnosticLogger } from "../../../../src/agent/diagnostics/diagnosticLogger"; +import { EtwDiagnosticLogger } from "../../../../src/agent/diagnostics/etwDiagnosticLogger"; +import { EtwWriter } from "../../../../src/agent/diagnostics/writers/etwWriter"; describe("agent/diagnostics/diagnosticLogger", () => { let sandbox: sinon.SinonSandbox; @@ -21,3 +23,143 @@ describe("agent/diagnostics/diagnosticLogger", () => { assert(consoleStub.calledOnce); }); }); + +describe("agent/diagnostics/etwDiagnosticLogger", () => { + let sandbox: sinon.SinonSandbox; + + before(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe("constructor", () => { + it("should initialize with correct instrumentationKey", () => { + const instrumentationKey = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333"; + + // Create the logger + const logger = new EtwDiagnosticLogger(instrumentationKey); + + // Verify instrumentationKey is set correctly + assert.strictEqual((logger as any)._instrumentationKey, instrumentationKey); + + // Verify EtwWriter is initialized + assert.ok((logger as any)._agentLogger instanceof EtwWriter); + }); + }); + + describe("logMessage", () => { + it("should call EtwWriter.log with message and metadata", () => { + const instrumentationKey = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333"; + const messageId = "testMessageId"; + const message = "test message"; + + // Create a stub for EtwWriter.log + const logStub = sandbox.stub(EtwWriter.prototype, "log"); + + // Create the logger + const logger = new EtwDiagnosticLogger(instrumentationKey); + + // Call logMessage + logger.logMessage({ message, messageId }); + + // Verify log was called with correct parameters + assert.strictEqual(logStub.callCount, 1); + assert.strictEqual(logStub.firstCall.args[0], message); + + // Verify metadata array + const metadataArg = logStub.firstCall.args[1]; + assert.ok(Array.isArray(metadataArg)); + assert.strictEqual(metadataArg.length, 6); // 5 base metadata fields + messageId + assert.strictEqual(metadataArg[5], messageId); // messageId is pushed last + }); + + it("should handle missing messageId", () => { + const instrumentationKey = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333"; + const message = "test message"; + + // Create a stub for EtwWriter.log + const logStub = sandbox.stub(EtwWriter.prototype, "log"); + + // Create the logger + const logger = new EtwDiagnosticLogger(instrumentationKey); + + // Call logMessage without messageId + logger.logMessage({ message }); + + // Verify log was called with correct parameters + assert.strictEqual(logStub.callCount, 1); + assert.strictEqual(logStub.firstCall.args[0], message); + + // Verify metadata array + const metadataArg = logStub.firstCall.args[1]; + assert.ok(Array.isArray(metadataArg)); + assert.strictEqual(metadataArg.length, 6); // 5 base metadata fields + empty messageId + assert.strictEqual(metadataArg[5], ""); // Empty string for missing messageId + }); + }); + + describe("_getMetadata", () => { + it("should return metadata array in correct order", () => { + const instrumentationKey = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333"; + + // Save original environment variables + const originalEnv = { ...process.env }; + + // Mock environment variables + process.env.WEBSITE_SITE_NAME = "testSiteName"; + process.env.ApplicationInsightsAgent_EXTENSION_VERSION = "testExtensionVersion"; + process.env.WEBSITE_OWNER_NAME = "testSubscriptionId+testResourceGroup"; + + // Create the logger + const logger = new EtwDiagnosticLogger(instrumentationKey); + + // Access private method using type assertion + const getMetadata = (logger as any)._getMetadata.bind(logger); + const metadata = getMetadata(); + + // Verify metadata array structure and order + assert.strictEqual(metadata.length, 5); + assert.strictEqual(metadata[0], "testExtensionVersion"); // _extensionVersion + assert.strictEqual(metadata[1], "testSubscriptionId"); // _subscriptionId + assert.strictEqual(metadata[2], "testSiteName"); // _siteName + assert.strictEqual(metadata[3], (logger as any)._sdkVersion); // _sdkVersion + assert.strictEqual(metadata[4], instrumentationKey); // _instrumentationKey + + // Restore environment variables + process.env = originalEnv; + }); + + it("should handle missing environment variables", () => { + const instrumentationKey = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333"; + + // Save original environment variables + const originalEnv = { ...process.env }; + + // Clear relevant environment variables + delete process.env.WEBSITE_SITE_NAME; + delete process.env.ApplicationInsightsAgent_EXTENSION_VERSION; + delete process.env.WEBSITE_OWNER_NAME; + + // Create the logger + const logger = new EtwDiagnosticLogger(instrumentationKey); + + // Access private method using type assertion + const getMetadata = (logger as any)._getMetadata.bind(logger); + const metadata = getMetadata(); + + // Verify metadata array structure and order + assert.strictEqual(metadata.length, 5); + assert.strictEqual(metadata[0], undefined); // _extensionVersion + assert.strictEqual(metadata[1], null); // _subscriptionId is null when WEBSITE_OWNER_NAME is missing + assert.strictEqual(metadata[2], undefined); // _siteName + assert.strictEqual(metadata[3], (logger as any)._sdkVersion); // _sdkVersion + assert.strictEqual(metadata[4], instrumentationKey); // _instrumentationKey + + // Restore environment variables + process.env = originalEnv; + }); + }); +}); From af581e2256833b19d5f9fd3ac1f1bab8c056df7f Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 17:51:29 -0700 Subject: [PATCH 03/10] Add etwWriter tests. --- .../diagnostics/diagnosticLogger.tests.ts | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts b/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts index 795319c18..3e5fc2bfa 100644 --- a/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts +++ b/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts @@ -3,9 +3,12 @@ import * as assert from "assert"; import * as sinon from "sinon"; +import * as fs from "fs"; +import * as path from "path"; import { DiagnosticLogger } from "../../../../src/agent/diagnostics/diagnosticLogger"; import { EtwDiagnosticLogger } from "../../../../src/agent/diagnostics/etwDiagnosticLogger"; import { EtwWriter } from "../../../../src/agent/diagnostics/writers/etwWriter"; +import { Util } from "../../../../src/shared/util"; describe("agent/diagnostics/diagnosticLogger", () => { let sandbox: sinon.SinonSandbox; @@ -163,3 +166,229 @@ describe("agent/diagnostics/etwDiagnosticLogger", () => { }); }); }); + +describe("agent/diagnostics/writers/etwWriter", () => { + let sandbox: sinon.SinonSandbox; + + before(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe("constructor", () => { + it("should initialize without ETW module when fs.accessSync throws", () => { + // Mock fs.accessSync to throw + sandbox.stub(fs, "accessSync").throws(new Error("Directory not accessible")); + + // Mock console.log + const consoleLogStub = sandbox.stub(console, "log"); + + const writer = new EtwWriter(); + + // Verify ETW module is not loaded + assert.strictEqual((writer as any)._etwModule, undefined); + assert.ok(consoleLogStub.calledWith('AppInsightsAgent: ETW could not be loaded')); + }); + + it("should handle errors when loading ETW module", () => { + // Mock process.versions.node + const originalNodeVersion = process.versions.node; + Object.defineProperty(process.versions, 'node', { + value: '16.0.0', + configurable: true + }); + + // Mock console.log + const consoleLogStub = sandbox.stub(console, "log"); + + // Force constructor to throw an error + sandbox.stub(EtwWriter.prototype as any, "_loadEtwModule").throws(new Error("Test error")); + + const writer = new EtwWriter(); + + // Verify ETW module is not loaded + assert.strictEqual((writer as any)._etwModule, undefined); + assert.ok(consoleLogStub.calledWith(sinon.match(/Could not load ETW. Defaulting to console logging/))); + + // Restore Node.js version + Object.defineProperty(process.versions, 'node', { + value: originalNodeVersion, + configurable: true + }); + }); + }); + + describe("log", () => { + it("should call logInfoEvent when ETW module exists", () => { + // Create a writer with mock ETW module + const mockEtwModule = { + logInfoEvent: sandbox.stub() + }; + + const writer = new EtwWriter(); + (writer as any)._etwModule = mockEtwModule; + + const message = "Test message"; + const optional = ["metadata1", "metadata2"]; + + writer.log(message, optional); + + // Verify logInfoEvent was called with correct parameters + assert.ok(mockEtwModule.logInfoEvent.calledOnce); + assert.ok(mockEtwModule.logInfoEvent.calledWith(message, ...optional)); + }); + + it("should log to console when ETW module does not exist", () => { + // Create a writer without ETW module + const writer = new EtwWriter(); + (writer as any)._etwModule = undefined; + + // Mock console.log + const consoleLogStub = sandbox.stub(console, "log"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified message"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + const message = "Test message"; + + writer.log(message); + + // Verify console.log was called with stringified message + assert.ok(consoleLogStub.calledOnce); + assert.ok(stringifyStub.calledWith(message)); + assert.ok(consoleLogStub.calledWith("stringified message")); + }); + + it("should handle optional parameters when ETW module does not exist", () => { + // Create a writer without ETW module + const writer = new EtwWriter(); + (writer as any)._etwModule = undefined; + + // Mock console.log + const consoleLogStub = sandbox.stub(console, "log"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified message"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + const message = "Test message"; + const optional = ["metadata1", "metadata2"]; + + writer.log(message, optional); + + // Verify console.log was called with stringified message + assert.ok(consoleLogStub.calledOnce); + assert.ok(stringifyStub.calledWith(message)); + assert.ok(consoleLogStub.calledWith("stringified message")); + }); + }); + + describe("error", () => { + it("should call logErrEvent when ETW module exists", () => { + // Create a writer with mock ETW module + const mockEtwModule = { + logErrEvent: sandbox.stub() + }; + + const writer = new EtwWriter(); + (writer as any)._etwModule = mockEtwModule; + + const message = "Error message"; + const optional = ["metadata1", "metadata2"]; + + writer.error(message, optional); + + // Verify logErrEvent was called with correct parameters + assert.ok(mockEtwModule.logErrEvent.calledOnce); + assert.ok(mockEtwModule.logErrEvent.calledWith(message, ...optional)); + }); + + it("should log to console.error when ETW module does not exist", () => { + // Create a writer without ETW module + const writer = new EtwWriter(); + (writer as any)._etwModule = undefined; + + // Mock console.error + const consoleErrorStub = sandbox.stub(console, "error"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified error message"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + const message = "Error message"; + + writer.error(message); + + // Verify console.error was called with stringified message + assert.ok(consoleErrorStub.calledOnce); + assert.ok(stringifyStub.calledWith(message)); + assert.ok(consoleErrorStub.calledWith("stringified error message")); + }); + + it("should handle optional parameters when ETW module does not exist", () => { + // Create a writer without ETW module + const writer = new EtwWriter(); + (writer as any)._etwModule = undefined; + + // Mock console.error + const consoleErrorStub = sandbox.stub(console, "error"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified error message"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + const message = "Error message"; + const optional = ["metadata1", "metadata2"]; + + writer.error(message, optional); + + // Verify console.error was called with stringified message + assert.ok(consoleErrorStub.calledOnce); + assert.ok(stringifyStub.calledWith(message)); + assert.ok(consoleErrorStub.calledWith("stringified error message")); + }); + }); + + describe("_loadEtwModule", () => { + it("should return undefined when directory is not accessible", () => { + // Mock fs.accessSync to throw + sandbox.stub(fs, "accessSync").throws(new Error("Directory not accessible")); + + const writer = new EtwWriter(); + const result = (writer as any)._loadEtwModule(16); // Using Node.js v16 + + assert.strictEqual(result, undefined); + }); + + it("should handle require throwing an error", () => { + // Mock path.join + const mockPath = "/mock/path/to/etw/etw_16"; + sandbox.stub(path, "join").returns(mockPath); + + // Mock fs.accessSync to not throw + sandbox.stub(fs, "accessSync").returns(undefined); + + // Mock require to throw + const requireStub = sandbox.stub().throws(new Error("Module loading error")); + const originalRequire = require; + (global as any).require = requireStub; + + const writer = new EtwWriter(); + const result = (writer as any)._loadEtwModule(16); // Using Node.js v16 + + // Verify result is undefined + assert.strictEqual(result, undefined); + + // Restore require + (global as any).require = originalRequire; + }); + }); +}); From 12dc625f624c1ede94a0cce7e3e9f70fda0f94ac Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 17:56:51 -0700 Subject: [PATCH 04/10] Update consoleWriter tests. --- .../diagnostics/diagnosticLogger.tests.ts | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts b/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts index 3e5fc2bfa..7987e7894 100644 --- a/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts +++ b/test/unitTests/agent/diagnostics/diagnosticLogger.tests.ts @@ -8,6 +8,7 @@ import * as path from "path"; import { DiagnosticLogger } from "../../../../src/agent/diagnostics/diagnosticLogger"; import { EtwDiagnosticLogger } from "../../../../src/agent/diagnostics/etwDiagnosticLogger"; import { EtwWriter } from "../../../../src/agent/diagnostics/writers/etwWriter"; +import { ConsoleWriter } from "../../../../src/agent/diagnostics/writers/consoleWriter"; import { Util } from "../../../../src/shared/util"; describe("agent/diagnostics/diagnosticLogger", () => { @@ -392,3 +393,167 @@ describe("agent/diagnostics/writers/etwWriter", () => { }); }); }); + +describe("agent/diagnostics/writers/consoleWriter", () => { + let sandbox: sinon.SinonSandbox; + + before(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe("log", () => { + it("should call console.log with stringified message", () => { + // Mock console.log + const consoleLogStub = sandbox.stub(console, "log"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified message"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + // Create a ConsoleWriter instance + const writer = new ConsoleWriter(); + + // Call log with various types of messages + writer.log("string message"); + writer.log({ key: "object message" }); + writer.log(123); + writer.log(null); + writer.log(undefined); + + // Verify console.log was called with stringified message for each call + assert.strictEqual(consoleLogStub.callCount, 5); + assert.strictEqual(stringifyStub.callCount, 5); + assert.ok(stringifyStub.calledWith("string message")); + assert.ok(stringifyStub.calledWith({ key: "object message" })); + assert.ok(stringifyStub.calledWith(123)); + assert.ok(stringifyStub.calledWith(null)); + assert.ok(stringifyStub.calledWith(undefined)); + assert.ok(consoleLogStub.alwaysCalledWith("stringified message")); + }); + + it("should handle optional parameters", () => { + // Mock console.log + const consoleLogStub = sandbox.stub(console, "log"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified message"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + // Create a ConsoleWriter instance + const writer = new ConsoleWriter(); + + // Call log with optional parameters + writer.log("message", "param1", "param2", { param: 3 }); + + // Verify console.log was called with stringified message + assert.strictEqual(consoleLogStub.callCount, 1); + assert.strictEqual(stringifyStub.callCount, 1); + assert.ok(stringifyStub.calledWith("message")); + assert.ok(consoleLogStub.calledWith("stringified message")); + }); + + it("should handle message being undefined", () => { + // Mock console.log + const consoleLogStub = sandbox.stub(console, "log"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified undefined"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + // Create a ConsoleWriter instance + const writer = new ConsoleWriter(); + + // Call log without a message + writer.log(); + + // Verify console.log was called with stringified undefined + assert.strictEqual(consoleLogStub.callCount, 1); + assert.strictEqual(stringifyStub.callCount, 1); + assert.ok(stringifyStub.calledWith(undefined)); + assert.ok(consoleLogStub.calledWith("stringified undefined")); + }); + }); + + describe("error", () => { + it("should call console.error with stringified message", () => { + // Mock console.error + const consoleErrorStub = sandbox.stub(console, "error"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified error message"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + // Create a ConsoleWriter instance + const writer = new ConsoleWriter(); + + // Call error with various types of messages + writer.error("string error"); + writer.error({ key: "object error" }); + writer.error(456); + writer.error(null); + writer.error(undefined); + + // Verify console.error was called with stringified message for each call + assert.strictEqual(consoleErrorStub.callCount, 5); + assert.strictEqual(stringifyStub.callCount, 5); + assert.ok(stringifyStub.calledWith("string error")); + assert.ok(stringifyStub.calledWith({ key: "object error" })); + assert.ok(stringifyStub.calledWith(456)); + assert.ok(stringifyStub.calledWith(null)); + assert.ok(stringifyStub.calledWith(undefined)); + assert.ok(consoleErrorStub.alwaysCalledWith("stringified error message")); + }); + + it("should handle optional parameters", () => { + // Mock console.error + const consoleErrorStub = sandbox.stub(console, "error"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified error message"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + // Create a ConsoleWriter instance + const writer = new ConsoleWriter(); + + // Call error with optional parameters + writer.error("error message", "param1", "param2", { param: 3 }); + + // Verify console.error was called with stringified message + assert.strictEqual(consoleErrorStub.callCount, 1); + assert.strictEqual(stringifyStub.callCount, 1); + assert.ok(stringifyStub.calledWith("error message")); + assert.ok(consoleErrorStub.calledWith("stringified error message")); + }); + + it("should handle message being undefined", () => { + // Mock console.error + const consoleErrorStub = sandbox.stub(console, "error"); + + // Mock Util.getInstance().stringify + const stringifyStub = sandbox.stub(); + stringifyStub.returns("stringified undefined"); + sandbox.stub(Util, "getInstance").returns({ stringify: stringifyStub } as any); + + // Create a ConsoleWriter instance + const writer = new ConsoleWriter(); + + // Call error without a message + writer.error(); + + // Verify console.error was called with stringified undefined + assert.strictEqual(consoleErrorStub.callCount, 1); + assert.strictEqual(stringifyStub.callCount, 1); + assert.ok(stringifyStub.calledWith(undefined)); + assert.ok(consoleErrorStub.calledWith("stringified undefined")); + }); + }); +}); From cbe7c68db704c238c48b62daff32a3a6e54b698a Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 18:07:18 -0700 Subject: [PATCH 05/10] Add file system helper tests. --- test/unitTests/shared/util.tests.ts | 197 ++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/test/unitTests/shared/util.tests.ts b/test/unitTests/shared/util.tests.ts index 67f3c1c92..c91dcc006 100644 --- a/test/unitTests/shared/util.tests.ts +++ b/test/unitTests/shared/util.tests.ts @@ -161,6 +161,43 @@ describe("Library/Util", () => { await fileSystemHelper.confirmDirExists(newDir); assert.ok(fs.existsSync(newDir), "Directory should be created"); }); + + it("should handle race condition where directory is created between check and creation", async () => { + const racyDir = path.join(tempDir, "racy-dir"); + + // Mock lstat to throw ENOENT but mkdir to throw EEXIST to simulate race condition + const lstatStub = sandbox.stub(fileSystemHelper, "lstatAsync"); + lstatStub.rejects({ code: "ENOENT" }); + + const mkdirStub = sandbox.stub(fileSystemHelper, "mkdirAsync"); + mkdirStub.rejects({ code: "EEXIST" }); + + // Should not throw exception + await fileSystemHelper.confirmDirExists(racyDir); + assert.ok(lstatStub.calledOnce, "lstatAsync should be called"); + assert.ok(mkdirStub.calledOnce, "mkdirAsync should be called"); + }); + + it("should propagate non-EEXIST errors from mkdir", async () => { + const errorDir = path.join(tempDir, "error-dir"); + + // Mock lstat to throw ENOENT and mkdir to throw EPERM + const lstatStub = sandbox.stub(fileSystemHelper, "lstatAsync"); + lstatStub.rejects({ code: "ENOENT" }); + + const mkdirStub = sandbox.stub(fileSystemHelper, "mkdirAsync"); + mkdirStub.rejects({ code: "EPERM", message: "Permission denied" }); + + try { + await fileSystemHelper.confirmDirExists(errorDir); + assert.fail("Should have thrown an error"); + } catch (err) { + assert.strictEqual(err.code, "EPERM"); + } + + assert.ok(lstatStub.calledOnce, "lstatAsync should be called"); + assert.ok(mkdirStub.calledOnce, "mkdirAsync should be called"); + }); }); describe("#getShallowDirectorySize()", () => { @@ -202,6 +239,44 @@ describe("Library/Util", () => { const size = await fileSystemHelper.getShallowDirectorySize(emptyDir); assert.strictEqual(size, 0); }); + + it("should handle directories containing both files and subdirectories", async () => { + // Create mixed content directory + const mixedDir = path.join(tempDir, "mixed-dir"); + if (!fs.existsSync(mixedDir)) { + fs.mkdirSync(mixedDir, { recursive: true }); + } + + // Create files in the root + const file1 = path.join(mixedDir, "file1.txt"); + const file2 = path.join(mixedDir, "file2.txt"); + fs.writeFileSync(file1, "file1-content"); + fs.writeFileSync(file2, "file2-content"); + + // Create subdirectory with files + const subDir = path.join(mixedDir, "subdir"); + if (!fs.existsSync(subDir)) { + fs.mkdirSync(subDir, { recursive: true }); + } + const subFile = path.join(subDir, "subfile.txt"); + fs.writeFileSync(subFile, "subfile-content"); + + const size = await fileSystemHelper.getShallowDirectorySize(mixedDir); + assert.strictEqual(size, "file1-content".length + "file2-content".length, + "Should only include size of root level files"); + }); + + it("should handle errors in readdir", async () => { + const readdirStub = sandbox.stub(fileSystemHelper, "readdirAsync"); + readdirStub.rejects(new Error("Readdir error")); + + try { + await fileSystemHelper.getShallowDirectorySize("non-existent-dir"); + assert.fail("Should have thrown an error"); + } catch (err) { + assert.strictEqual(err.message, "Readdir error"); + } + }); }); describe("#getShallowDirectorySizeSync()", () => { @@ -227,6 +302,61 @@ describe("Library/Util", () => { const size = fileSystemHelper.getShallowDirectorySizeSync(emptyDir); assert.strictEqual(size, 0); }); + + it("should only count files at root level and ignore subdirectories", () => { + // Create mixed content directory + const mixedSyncDir = path.join(tempDir, "mixed-sync-dir"); + if (!fs.existsSync(mixedSyncDir)) { + fs.mkdirSync(mixedSyncDir, { recursive: true }); + } + + // Create files in the root + const file1 = path.join(mixedSyncDir, "file1.txt"); + const file2 = path.join(mixedSyncDir, "file2.txt"); + fs.writeFileSync(file1, "file1-sync-content"); + fs.writeFileSync(file2, "file2-sync-content"); + + // Create subdirectory with files + const subDir = path.join(mixedSyncDir, "subdir"); + if (!fs.existsSync(subDir)) { + fs.mkdirSync(subDir, { recursive: true }); + } + const subFile = path.join(subDir, "subfile.txt"); + fs.writeFileSync(subFile, "subfile-sync-content"); + + const size = fileSystemHelper.getShallowDirectorySizeSync(mixedSyncDir); + assert.strictEqual(size, "file1-sync-content".length + "file2-sync-content".length, + "Should only include size of root level files"); + }); + + it("should handle errors in readdirSync", () => { + const readdirSyncStub = sandbox.stub(fs, "readdirSync"); + readdirSyncStub.throws(new Error("ReaddirSync error")); + + try { + fileSystemHelper.getShallowDirectorySizeSync("non-existent-dir"); + assert.fail("Should have thrown an error"); + } catch (err) { + assert.strictEqual(err.message, "ReaddirSync error"); + } + }); + + // it("should handle errors in statSync", () => { + // const readdirSyncStub = sandbox.stub(fs, "readdirSync"); + // // Create mock file entries + // const mockFiles = ["file1.txt", "file2.txt"]; + // readdirSyncStub.returns(mockFiles); + + // const statSyncStub = sandbox.stub(fs, "statSync"); + // statSyncStub.throws(new Error("StatSync error")); + + // try { + // fileSystemHelper.getShallowDirectorySizeSync("test-dir"); + // assert.fail("Should have thrown an error"); + // } catch (err) { + // assert.strictEqual(err.message, "StatSync error"); + // } + // }); }); describe("#getShallowFileSize()", () => { @@ -242,6 +372,35 @@ describe("Library/Util", () => { const result = await fileSystemHelper.getShallowFileSize(tempDir); assert.strictEqual(result, undefined); }); + + it("should handle empty files", async () => { + // Create empty file + const emptyFilePath = path.join(tempDir, "empty-file.txt"); + fs.writeFileSync(emptyFilePath, ""); + + const size = await fileSystemHelper.getShallowFileSize(emptyFilePath); + assert.strictEqual(size, 0); + }); + + it("should handle binary files", async () => { + // Create binary file + const binaryFilePath = path.join(tempDir, "binary-file.bin"); + const binaryContent = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]); + fs.writeFileSync(binaryFilePath, binaryContent); + + const size = await fileSystemHelper.getShallowFileSize(binaryFilePath); + assert.strictEqual(size, binaryContent.length); + }); + + it("should handle large files", async () => { + // Create a moderately large file (1MB) + const largeFilePath = path.join(tempDir, "large-file.txt"); + const largeContent = Buffer.alloc(1024 * 1024, 'a'); + fs.writeFileSync(largeFilePath, largeContent); + + const size = await fileSystemHelper.getShallowFileSize(largeFilePath); + assert.strictEqual(size, largeContent.length); + }); }); describe("promisified fs functions", () => { @@ -325,6 +484,44 @@ describe("Library/Util", () => { await fileSystemHelper.unlinkAsync(fileToDelete); assert.ok(!fs.existsSync(fileToDelete), "File should be deleted"); }); + + it("should handle errors in promisified functions", async () => { + // Test error handling for stat + try { + await fileSystemHelper.statAsync("non-existent-file.txt"); + assert.fail("Should have thrown an error"); + } catch (err) { + assert.ok(err, "Error should be thrown"); + assert.strictEqual(err.code, "ENOENT"); + } + + // Test error handling for access + try { + await fileSystemHelper.accessAsync("non-existent-file.txt"); + assert.fail("Should have thrown an error"); + } catch (err) { + assert.ok(err, "Error should be thrown"); + assert.strictEqual(err.code, "ENOENT"); + } + + // Test error handling for readFile + try { + await fileSystemHelper.readFileAsync("non-existent-file.txt"); + assert.fail("Should have thrown an error"); + } catch (err) { + assert.ok(err, "Error should be thrown"); + assert.strictEqual(err.code, "ENOENT"); + } + + // Test error handling for unlink + try { + await fileSystemHelper.unlinkAsync("non-existent-file.txt"); + assert.fail("Should have thrown an error"); + } catch (err) { + assert.ok(err, "Error should be thrown"); + assert.strictEqual(err.code, "ENOENT"); + } + }); }); }); }); From 20dd2e6a27c25757497c44732be4471da7bd2ba4 Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 18:29:22 -0700 Subject: [PATCH 06/10] Add correlatoinContextManager tests. --- .../shim/correlationContextManger.tests.ts | 695 ++++++++++++------ 1 file changed, 489 insertions(+), 206 deletions(-) diff --git a/test/unitTests/shim/correlationContextManger.tests.ts b/test/unitTests/shim/correlationContextManger.tests.ts index 5b81b5818..4a4c05335 100644 --- a/test/unitTests/shim/correlationContextManger.tests.ts +++ b/test/unitTests/shim/correlationContextManger.tests.ts @@ -2,17 +2,21 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. import assert = require("assert"); import sinon = require("sinon"); -import { SpanContext } from "@opentelemetry/api"; +import * as events from "events"; +import { SpanContext, context, trace, diag, DiagLogger } from "@opentelemetry/api"; +import { TraceState } from "@opentelemetry/core"; +import { Span } from "@opentelemetry/sdk-trace-base"; import * as azureFunctionTypes from "@azure/functions-old"; import { CorrelationContextManager } from '../../../src/shim/correlationContextManager'; -import { ICorrelationContext } from "../../../src/shim/types"; +import { ICorrelationContext, ITraceparent, ITracestate } from "../../../src/shim/types"; import { HttpRequest, InvocationContext, TraceContext } from "@azure/functions"; +import { Util } from "../../../src/shared/util"; - +// Test fixtures const customProperties = { getProperty(prop: string) { return "" }, setProperty(prop: string) { return "" }, -} +}; const testContext: ICorrelationContext = { operation: { @@ -53,255 +57,534 @@ const testContext2: ICorrelationContext = { customProperties } -// Test getCurrentContext -describe("#getCurrentContext()", () => { - it("should return the context if in a context", (done) => { - CorrelationContextManager.runWithContext(testContext, () => { +describe("CorrelationContextManager", () => { + let sandbox: sinon.SinonSandbox; + let diagWarnStub: sinon.SinonStub; + let diagInfoStub: sinon.SinonStub; + let diagErrorStub: sinon.SinonStub; + let utilDumpObjStub: sinon.SinonStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + + // Stub diag methods to prevent console output during tests + diagWarnStub = sandbox.stub(diag, 'warn'); + diagInfoStub = sandbox.stub(diag, 'info'); + diagErrorStub = sandbox.stub(diag, 'error'); + + // Stub Util.dumpObj method + utilDumpObjStub = sandbox.stub(Util.getInstance(), 'dumpObj').returns('error dump'); + + // Ensure context is enabled before each test + CorrelationContextManager.reset(); + CorrelationContextManager.enable(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe("#spanToContextObject", () => { + it("should convert a SpanContext to an ICorrelationContext", () => { + const testSpanContext: SpanContext = { + traceId: "testtraceid", + spanId: "testspanid", + traceFlags: 1, + }; + const parentId = "parentid123"; + const name = "testOperation"; + const traceState = new TraceState("key1=value1,key2=value2"); + + const result = CorrelationContextManager.spanToContextObject(testSpanContext, parentId, name, traceState); + + assert.ok(result); + assert.strictEqual(result.operation.name, name); + assert.strictEqual(result.operation.id, testSpanContext.traceId); + assert.strictEqual(result.operation.parentId, parentId); + assert.deepStrictEqual(result.operation.tracestate.fieldmap, ["key1=value1", "key2=value2"]); + assert.strictEqual(result.operation.traceparent.spanId, testSpanContext.spanId); + assert.strictEqual(result.operation.traceparent.traceId, testSpanContext.traceId); + assert.strictEqual(result.operation.traceparent.traceFlag, testSpanContext.traceFlags.toString()); + }); + + it("should handle a null SpanContext", () => { + const result = CorrelationContextManager.spanToContextObject(null); + + assert.ok(result); + assert.strictEqual(result.operation.id, undefined); + assert.strictEqual(result.operation.traceparent.traceId, undefined); + }); + }); + + describe("#generateContextObject", () => { + it("should correctly generate a context object with all parameters", () => { + const operationId = "operationId123"; + const parentId = "parentId456"; + const operationName = "testOperation"; + const traceparent: ITraceparent = { + legacyRootId: "legacyId", + parentId: parentId, + traceId: operationId, + spanId: "spanId789", + traceFlag: "1", + version: "00" + }; + const tracestate = new TraceState("key1=value1,key2=value2"); + + const result = CorrelationContextManager.generateContextObject( + operationId, + parentId, + operationName, + traceparent, + tracestate + ); + + assert.ok(result); + assert.strictEqual(result.operation.name, operationName); + assert.strictEqual(result.operation.id, operationId); + assert.strictEqual(result.operation.parentId, parentId); + assert.deepStrictEqual(result.operation.traceparent, traceparent); + assert.deepStrictEqual(result.operation.tracestate.fieldmap, ["key1=value1", "key2=value2"]); + }); + + it("should provide usable stub custom properties", () => { + const operationId = "operationId123"; + const result = CorrelationContextManager.generateContextObject(operationId); + + assert.ok(result.customProperties); + assert.strictEqual(typeof result.customProperties.getProperty, "function"); + assert.strictEqual(typeof result.customProperties.setProperty, "function"); + assert.strictEqual(result.customProperties.getProperty("someKey"), ""); + }); + }); + + // Test getCurrentContext + describe("#getCurrentContext", () => { + it("should return the context if in a context", (done) => { + CorrelationContextManager.runWithContext(testContext, () => { process.nextTick(() => { assert.strictEqual(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext)); done(); }); }); - }); + }); - it("should return the context if called by an asychronous callback in a context", (done) => { - CorrelationContextManager.runWithContext(testContext2, () => { - process.nextTick(() => { - assert.strictEqual(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext2)); - done(); + it("should return the context if called by an asynchronous callback in a context", (done) => { + CorrelationContextManager.runWithContext(testContext2, () => { + process.nextTick(() => { + assert.strictEqual(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext2)); + done(); + }); }); }); - }); - it("should return the correct context to asynchronous callbacks occuring in parellel", (done) => { - CorrelationContextManager.runWithContext(testContext, () => { - process.nextTick(() => { - assert.strictEqual(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext)); + it("should return the correct context to asynchronous callbacks occurring in parallel", (done) => { + CorrelationContextManager.runWithContext(testContext, () => { + process.nextTick(() => { + assert.strictEqual(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext)); + }); }); + + CorrelationContextManager.runWithContext(testContext2, () => { + process.nextTick(() => { + assert.strictEqual(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext2)); + }); + }); + + setTimeout(() => done(), 10); }); - CorrelationContextManager.runWithContext(testContext2, () => { - process.nextTick(() => { - assert.strictEqual(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext2)); + + it("should create a new span if no active span exists", () => { + // Setup + const testSpanContext: SpanContext = { + traceId: "testtraceid", + spanId: "testspanid", + traceFlags: 1, + }; + const startSpanStub = sandbox.stub().returns({ + spanContext: () => testSpanContext, + name: "testSpan", + parentSpanId: "parentId123" }); + const getTracerStub = sandbox.stub(trace, 'getTracer').returns({ startSpan: startSpanStub } as any); + const getSpanStub = sandbox.stub(trace, 'getSpan').returns(null); + + // Execute + const result = CorrelationContextManager.getCurrentContext(); + + // Verify + assert.ok(result); + assert.ok(getTracerStub.calledOnce); + assert.ok(startSpanStub.calledOnce); }); - - setTimeout(() => done(), 10); }); -}); -// Test runWithContext -describe("#runWithContext()", () => { - it("should run the supplied function", () => { - CorrelationContextManager.enable(); - const fn = sinon.spy(); - CorrelationContextManager.runWithContext(testContext, fn); + // Test runWithContext + describe("#runWithContext", () => { + it("should run the supplied function", () => { + const fn = sinon.spy(); + CorrelationContextManager.runWithContext(testContext, fn); - assert(fn.calledOnce); + assert(fn.calledOnce); + }); + + it("should return the result of the supplied function", () => { + const expectedResult = { success: true }; + const fn = () => expectedResult; + + const result = CorrelationContextManager.runWithContext(testContext, fn); + + assert.deepStrictEqual(result, expectedResult); + }); + + it("should handle errors in context binding", () => { + // Setup + const error = new Error("Test error"); + sandbox.stub(trace, 'setSpanContext').throws(error); + const fn = sinon.spy(); + + // Execute + CorrelationContextManager.runWithContext(testContext, fn); + + // Verify + assert.ok(fn.calledOnce); + assert.ok(diagWarnStub.calledOnce); + assert.ok(utilDumpObjStub.calledOnce); + }); }); -}); -// Test wrapEmitter - -// Test wrapCallback -describe("#wrapCallback()", () => { - it("should return a function that calls the supplied function", () => { - const fn = sinon.spy(); - const wrappedFn = CorrelationContextManager.wrapCallback(fn); - wrappedFn(); - - assert.notEqual(wrappedFn, fn); - assert(fn.calledOnce); + // Test wrapEmitter + describe("#wrapEmitter", () => { + it("should call context.bind with the emitter", () => { + // Setup + const emitter = new events.EventEmitter(); + const contextBindStub = sandbox.stub(context, 'bind'); + + // Execute + CorrelationContextManager.wrapEmitter(emitter); + + // Verify + assert.ok(contextBindStub.calledOnce); + assert.ok(contextBindStub.calledWith(sinon.match.any, emitter)); + }); + + it("should handle errors when binding an emitter", () => { + // Setup + const emitter = new events.EventEmitter(); + const error = new Error("Binding error"); + sandbox.stub(context, 'bind').throws(error); + + // Execute + CorrelationContextManager.wrapEmitter(emitter); + + // Verify + assert.ok(diagWarnStub.calledOnce); + assert.ok(utilDumpObjStub.calledOnce); + }); + + it("should preserve context across emitter events", (done) => { + // Setup + const emitter = new events.EventEmitter(); + + // Execute + CorrelationContextManager.runWithContext(testContext, () => { + CorrelationContextManager.wrapEmitter(emitter); + + setTimeout(() => { + emitter.emit('test', 'data'); + }, 5); + }); + + // Listen for an event - should have the same context + emitter.on('test', (data) => { + const currentContext = CorrelationContextManager.getCurrentContext(); + assert.strictEqual(currentContext.operation.id, testContext.operation.id); + done(); + }); + }); }); - it("should return a function that restores the context at call-time into the supplied function", (done) => { - let sharedFn = () => { - assert.equal(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext)); - } + // Test wrapCallback + describe("#wrapCallback", () => { + it("should return a function that calls the supplied function", () => { + const fn = sinon.spy(); + const wrappedFn = CorrelationContextManager.wrapCallback(fn); + wrappedFn(); - CorrelationContextManager.runWithContext(testContext, () => { - sharedFn = CorrelationContextManager.wrapCallback(sharedFn); + assert.notEqual(wrappedFn, fn); + assert(fn.calledOnce); }); - CorrelationContextManager.runWithContext(testContext2, () => { - setTimeout(() => { - sharedFn(); - }, 8); - }); + it("should return a function that restores the context at call-time into the supplied function", (done) => { + let sharedFn = () => { + assert.equal(JSON.stringify(CorrelationContextManager.getCurrentContext()), JSON.stringify(testContext)); + }; - setTimeout(() => done(), 10); - }); -}); + CorrelationContextManager.runWithContext(testContext, () => { + sharedFn = CorrelationContextManager.wrapCallback(sharedFn); + }); -// Test startOperation -describe("#startOperation()", () => { - const testSpanContext: SpanContext = { - traceId: "testtraceid", - spanId: "testspanid", - traceFlags: 0, - }; - - const testFunctionTraceContext: azureFunctionTypes.TraceContext = { - traceparent: "00-testtraceid-testspanid", - tracestate: "", - attributes: {}, - }; - - const testFunctionTraceContextV4: TraceContext = { - traceParent: "00-testtraceid-testspanid", - traceState: "", - attributes: {}, - }; - - const testFunctionContextV4 = new InvocationContext({ - invocationId: "test", - functionName: "", - options: undefined, - traceContext: testFunctionTraceContextV4, - }); + CorrelationContextManager.runWithContext(testContext2, () => { + setTimeout(() => { + sharedFn(); + }, 8); + }); - const testFunctionContext: azureFunctionTypes.Context = { - invocationId: "test", - executionContext: { - invocationId: '', - functionName: '', - functionDirectory: '', - retryContext: undefined - }, - bindings: {}, - bindingData: { - invocationId: '' - }, - traceContext: testFunctionTraceContext, - bindingDefinitions: [], - log: { error() { }, warn() { }, info() { }, verbose() { } } as azureFunctionTypes.Logger, - done: () => { }, - }; - - const testRequest: azureFunctionTypes.HttpRequest = { - method: "GET", - url: "/search", - headers: { - host: "bing.com", - traceparent: testFunctionContext.traceContext.traceparent, - }, - query: { q: 'test' }, - params: {}, - user: null, - body: {}, - rawBody: {}, - bufferBody: undefined, - get(header: string) { return this.headers[header.toLowerCase()] }, - parseFormBody: undefined, - }; - - const testRequestHeadersV4: Record = { host: "bing.com", traceparent: testFunctionContextV4.traceContext.traceParent }; - - const testRequestV4 = new HttpRequest({ - method: "GET", - url: "http://bing.com/search", - headers: testRequestHeadersV4, - query: { q: 'test' }, - params: {}, - body: {}, + setTimeout(() => done(), 10); + }); + + it("should wrap callback with current context if no explicit context is provided", () => { + // Setup + const contextBindStub = sandbox.stub(context, 'bind').returns(() => {}); + const fn = () => {}; + + // Execute + CorrelationContextManager.runWithContext(testContext, () => { + CorrelationContextManager.wrapCallback(fn); + }); + + // Verify + assert.ok(contextBindStub.calledOnce); + assert.ok(contextBindStub.calledWith(sinon.match.any, fn)); + }); + + it("should handle errors when binding a callback", () => { + // Setup + const fn = () => {}; + const error = new Error("Binding error"); + sandbox.stub(context, 'bind').throws(error); + + // Execute + const result = CorrelationContextManager.wrapCallback(fn); + + // Verify + assert.strictEqual(result, fn); + assert.ok(diagErrorStub.calledOnce); + assert.ok(utilDumpObjStub.calledOnce); + }); }); - describe("#Azure Functions", () => { - it("should start a new context with the 2nd arg http request", () => { - const context = CorrelationContextManager.startOperation(testFunctionContext, testRequestV4); - assert.ok(context.operation); - assert.deepEqual(context.operation.id, testFunctionTraceContext.traceparent.split("-")[1]); - assert.deepEqual(context.operation.parentId, testFunctionTraceContext.traceparent.split("-")[2]); - assert.deepEqual( - `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, - testFunctionTraceContext.traceparent - ); + // Test startOperation + describe("#startOperation", () => { + const testSpanContext: SpanContext = { + traceId: "testtraceid", + spanId: "testspanid", + traceFlags: 1, + }; + + const testFunctionTraceContext: azureFunctionTypes.TraceContext = { + traceparent: "00-testtraceid-testspanid", + tracestate: "key1=value1,key2=value2", + attributes: {}, + }; + + const testFunctionTraceContextV4: TraceContext = { + traceParent: "00-testtraceid-testspanid", + traceState: "key1=value1,key2=value2", + attributes: {}, + }; + + const testFunctionContextV4 = new InvocationContext({ + invocationId: "test", + functionName: "", + options: undefined, + traceContext: testFunctionTraceContextV4, }); - it("should start a new context with 2nd arg string", () => { - const context = CorrelationContextManager.startOperation(testFunctionContext, "GET /foo"); - assert.ok(context.operation); - assert.deepEqual(context.operation.id, testFunctionTraceContext.traceparent.split("-")[1]); - assert.deepEqual(context.operation.parentId, testFunctionTraceContext.traceparent.split("-")[2]); - assert.deepEqual( - `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, - testFunctionTraceContext.traceparent - ); + const testFunctionContext: azureFunctionTypes.Context = { + invocationId: "test", + executionContext: { + invocationId: '', + functionName: '', + functionDirectory: '', + retryContext: undefined + }, + bindings: {}, + bindingData: { + invocationId: '' + }, + traceContext: testFunctionTraceContext, + bindingDefinitions: [], + log: { error() { }, warn() { }, info() { }, verbose() { } } as azureFunctionTypes.Logger, + done: () => { }, + }; + + const testRequest: azureFunctionTypes.HttpRequest = { + method: "GET", + url: "/search", + headers: { + host: "bing.com", + traceparent: testFunctionContext.traceContext.traceparent, + tracestate: testFunctionContext.traceContext.tracestate, + }, + query: { q: 'test' }, + params: {}, + user: null, + body: {}, + rawBody: {}, + bufferBody: undefined, + get(header: string) { return this.headers[header.toLowerCase()] }, + parseFormBody: undefined, + }; + + const testRequestHeadersV4: Record = { + host: "bing.com", + traceparent: testFunctionContextV4.traceContext.traceParent, + tracestate: testFunctionContextV4.traceContext.traceState + }; + + const testRequestV4 = new HttpRequest({ + method: "GET", + url: "http://bing.com/search", + headers: testRequestHeadersV4, + query: { q: 'test' }, + params: {}, + body: {}, }); - it("should start a new context with no request", () => { - const context = CorrelationContextManager.startOperation(testFunctionContext, "GET /test"); - assert.ok(context.operation); - assert.deepEqual(context.operation.id, testFunctionTraceContext.traceparent.split("-")[1]); - assert.deepEqual(context.operation.parentId, testFunctionTraceContext.traceparent.split("-")[2]); - assert.deepEqual( - `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, - testFunctionTraceContext.traceparent - ); + describe("with Span input", () => { + it("should start a new context using Span", () => { + // Setup + const mockSpan = { + spanContext: () => testSpanContext, + parentSpanId: "parentid123", + name: "testSpan" + } as Span; + + // Execute + const context = CorrelationContextManager.startOperation(mockSpan); + + // Verify + assert.ok(context); + assert.strictEqual(context.operation.id, testSpanContext.traceId); + assert.strictEqual(context.operation.parentId, mockSpan.parentSpanId); + }); }); - }); + + describe("with SpanContext input", () => { + it("should start a new context using SpanContext", () => { + const context = CorrelationContextManager.startOperation(testSpanContext); - describe("#Azure Functions V4", () => { - it("should start a new context with the 2nd arg http request", () => { - const context = CorrelationContextManager.startOperation(testFunctionContextV4, testRequestV4); - assert.ok(context.operation); - assert.deepEqual(context.operation.id, testFunctionTraceContextV4.traceParent.split("-")[1]); - assert.deepEqual(context.operation.parentId, testFunctionTraceContextV4.traceParent.split("-")[2]); - assert.deepEqual( - `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, - testFunctionTraceContextV4.traceParent - ); + assert.ok(context.operation); + assert.strictEqual(context.operation.id, testSpanContext.traceId); + }); }); - it("should start a new context with 2nd arg string", () => { - const context = CorrelationContextManager.startOperation(testFunctionContextV4, "GET /foo"); - assert.ok(context.operation); - assert.deepEqual(context.operation.id, testFunctionTraceContextV4.traceParent.split("-")[1]); - assert.deepEqual(context.operation.parentId, testFunctionTraceContextV4.traceParent.split("-")[2]); - assert.deepEqual( - `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, - testFunctionTraceContextV4.traceParent - ); - }); + describe("#Azure Functions", () => { + it("should start a new context with the 2nd arg http request", () => { + const context = CorrelationContextManager.startOperation(testFunctionContext, testRequestV4); + assert.ok(context.operation); + assert.strictEqual(context.operation.id, testFunctionTraceContext.traceparent.split("-")[1]); + assert.strictEqual(context.operation.parentId, testFunctionTraceContext.traceparent.split("-")[2]); + assert.strictEqual( + `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, + testFunctionTraceContext.traceparent + ); + }); - it("should start a new context with no request", () => { - const context = CorrelationContextManager.startOperation(testFunctionContextV4, "GET /test"); - assert.ok(context.operation); - assert.deepEqual(context.operation.id, testFunctionTraceContextV4.traceParent.split("-")[1]); - assert.deepEqual(context.operation.parentId, testFunctionTraceContextV4.traceParent.split("-")[2]); - assert.deepEqual( - `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, - testFunctionTraceContextV4.traceParent - ); + it("should start a new context with 2nd arg string", () => { + const context = CorrelationContextManager.startOperation(testFunctionContext, "GET /foo"); + assert.ok(context.operation); + assert.strictEqual(context.operation.id, testFunctionTraceContext.traceparent.split("-")[1]); + assert.strictEqual(context.operation.parentId, testFunctionTraceContext.traceparent.split("-")[2]); + assert.strictEqual( + `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, + testFunctionTraceContext.traceparent + ); + }); + + it("should start a new context with no request", () => { + const context = CorrelationContextManager.startOperation(testFunctionContext); + assert.ok(context.operation); + assert.strictEqual(context.operation.id, testFunctionTraceContext.traceparent.split("-")[1]); + assert.strictEqual(context.operation.parentId, testFunctionTraceContext.traceparent.split("-")[2]); + assert.strictEqual( + `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, + testFunctionTraceContext.traceparent + ); + }); }); - }); - describe("#SpanContext", () => { - it("should start a new context using SpanContext", () => { - const context = CorrelationContextManager.startOperation(testSpanContext, "GET /test"); + describe("#Azure Functions V4", () => { + it("should start a new context with the 2nd arg http request", () => { + const context = CorrelationContextManager.startOperation(testFunctionContextV4, testRequestV4); + assert.ok(context.operation); + assert.strictEqual(context.operation.id, testFunctionTraceContextV4.traceParent.split("-")[1]); + assert.strictEqual(context.operation.parentId, testFunctionTraceContextV4.traceParent.split("-")[2]); + assert.strictEqual( + `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, + testFunctionTraceContextV4.traceParent + ); + }); - assert.ok(context.operation); - assert.deepEqual(context.operation.id, testSpanContext.traceId); - assert.deepEqual(context.operation.parentId, context.operation.parentId); + it("should start a new context with 2nd arg string", () => { + const context = CorrelationContextManager.startOperation(testFunctionContextV4, "GET /foo"); + assert.ok(context.operation); + assert.strictEqual(context.operation.id, testFunctionTraceContextV4.traceParent.split("-")[1]); + assert.strictEqual(context.operation.parentId, testFunctionTraceContextV4.traceParent.split("-")[2]); + assert.strictEqual( + `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, + testFunctionTraceContextV4.traceParent + ); + }); + + it("should start a new context with no request", () => { + const context = CorrelationContextManager.startOperation(testFunctionContextV4, "GET /test"); + assert.ok(context.operation); + assert.strictEqual(context.operation.id, testFunctionTraceContextV4.traceParent.split("-")[1]); + assert.strictEqual(context.operation.parentId, testFunctionTraceContextV4.traceParent.split("-")[2]); + assert.strictEqual( + `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, + testFunctionTraceContextV4.traceParent + ); + }); }); - }); describe("#headers", () => { it("should start a new context using the headers from an HTTP request", () => { const context = CorrelationContextManager.startOperation(testRequest, "GET /test"); - - assert.ok(context.operation); - assert.deepEqual(context.operation.id, testFunctionTraceContext?.traceparent?.split("-")[1]); - assert.deepEqual(context.operation.parentId, testFunctionTraceContext.traceparent.split("-")[2]); - assert.deepEqual( - `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, - testFunctionTraceContext.traceparent - ); + assert.ok(context.operation); + assert.strictEqual(context.operation.id, testFunctionTraceContext.traceparent.split("-")[1]); + assert.strictEqual(context.operation.parentId, testFunctionTraceContext.traceparent.split("-")[2]); + assert.strictEqual( + `${context.operation.traceparent.version}-${context.operation.traceparent.traceId}-${context.operation.traceparent.spanId}`, + testFunctionTraceContext.traceparent + ); + }); + + it("should handle HTTP request with request-id fallback", () => { + // Setup + const mockHeaders = { + get: sandbox.stub() + }; + mockHeaders.get.withArgs("traceparent").returns(null); + mockHeaders.get.withArgs("request-id").returns("00-requestid-requestparent-01"); + + const mockRequest = { + headers: mockHeaders + }; + + // Execute + const context = CorrelationContextManager.startOperation(mockRequest as any); + + // Verify + assert.ok(context); + assert.strictEqual(context.operation.id, "requestid"); + assert.strictEqual(context.operation.parentId, "requestparent"); + }); + + it("should warn when invalid arguments are provided", () => { + // Execute + const context = CorrelationContextManager.startOperation(null); + + // Verify + assert.strictEqual(context, null); + assert.ok(diagWarnStub.calledWith("startOperation was called with invalid arguments")); + }); }); }); - - /** - * This test must occur last as it will disable context - */ + + // This test must occur last as it will disable context describe("#Context.Disable", () => { it("should return null if the context is disabled", () => { CorrelationContextManager.disable(); From 608fc2c7d29bd22a91dde3c7acd431d1e85700be Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 18:50:11 -0700 Subject: [PATCH 07/10] Update telemetryClient tests. --- test/unitTests/shim/telemetryClient.tests.ts | 93 +++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/test/unitTests/shim/telemetryClient.tests.ts b/test/unitTests/shim/telemetryClient.tests.ts index 5d5ce0acb..064067c1d 100644 --- a/test/unitTests/shim/telemetryClient.tests.ts +++ b/test/unitTests/shim/telemetryClient.tests.ts @@ -3,9 +3,9 @@ import * as assert from "assert"; import * as nock from "nock"; import * as sinon from "sinon"; -import { Context, ProxyTracerProvider, trace, metrics } from "@opentelemetry/api"; +import { Context, ProxyTracerProvider, trace, metrics, diag } from "@opentelemetry/api"; import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base"; -import { DependencyTelemetry, RequestTelemetry } from "../../../src/declarations/contracts"; +import { DependencyTelemetry, RequestTelemetry, TelemetryType } from "../../../src/declarations/contracts"; import { TelemetryClient } from "../../../src/shim/telemetryClient"; import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"; import { AzureMonitorExporterOptions, AzureMonitorMetricExporter } from "@azure/monitor-opentelemetry-exporter"; @@ -13,6 +13,7 @@ import { MeterProvider, PeriodicExportingMetricReader, PeriodicExportingMetricRe import { LogRecord, LogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs"; import { logs } from "@opentelemetry/api-logs"; import { SEMATTRS_RPC_SYSTEM } from "@opentelemetry/semantic-conventions"; +import Config = require("../../../src/shim/shim-config"); describe("shim/TelemetryClient", () => { let client: TelemetryClient; @@ -23,6 +24,8 @@ describe("shim/TelemetryClient", () => { let metricProvider: MeterProvider; let logProcessor: TestLogProcessor; let sandbox: sinon.SinonSandbox; + let diagErrorStub: sinon.SinonStub; + let diagWarnStub: sinon.SinonStub; before(() => { sandbox = sinon.createSandbox(); @@ -51,6 +54,11 @@ describe("shim/TelemetryClient", () => { metricProvider = metrics.getMeterProvider() as MeterProvider; }); + beforeEach(() => { + diagErrorStub = sandbox.stub(diag, 'error'); + diagWarnStub = sandbox.stub(diag, 'warn'); + }); + afterEach(() => { sandbox.restore(); testProcessor.spansProcessed = []; @@ -110,6 +118,66 @@ describe("shim/TelemetryClient", () => { } } + describe("#unsupported and deprecated methods", () => { + it("track throws error", () => { + assert.throws(() => { + client.track({ name: "test" } as any, "Event" as any); + }, /Not implemented/); + }); + + it("addTelemetryProcessor should warn", () => { + client.addTelemetryProcessor(() => true); + assert.ok(diagWarnStub.calledOnce); + }); + + it("getAuthorizationHandler should warn", () => { + client.getAuthorizationHandler(new Config()); + assert.ok(diagWarnStub.calledOnce); + }); + + it("setAutoPopulateAzureProperties should do nothing", () => { + // This is a no-op, so just verify it doesn't throw + assert.doesNotThrow(() => { + client.setAutoPopulateAzureProperties(); + }); + }); + + it("getStatsbeat should return null", () => { + const result = client.getStatsbeat(); + assert.strictEqual(result, null); + }); + + it("setUseDiskRetryCaching throws error", () => { + assert.throws(() => { + client.setUseDiskRetryCaching(true); + }, /Not implemented/); + }); + + it("clearTelemetryProcessors throws error", () => { + assert.throws(() => { + client.clearTelemetryProcessors(); + }, /Not implemented/); + }); + + it("trackNodeHttpRequestSync should warn", () => { + client.trackNodeHttpRequestSync({} as any); + assert.ok(diagWarnStub.calledOnce); + assert.ok(diagWarnStub.calledWith("trackNodeHttpRequestSync is not implemented and is a no-op. Please use trackRequest instead.")); + }); + + it("trackNodeHttpRequest should warn", () => { + client.trackNodeHttpRequest({} as any); + assert.ok(diagWarnStub.calledOnce); + assert.ok(diagWarnStub.calledWith("trackNodeHttpRequest is not implemented and is a no-op. Please use trackRequest instead.")); + }); + + it("trackNodeHttpDependency should warn", () => { + client.trackNodeHttpDependency({} as any); + assert.ok(diagWarnStub.calledOnce); + assert.ok(diagWarnStub.calledWith("trackNodeHttpDependency is not implemented and is a no-op. Please use trackDependency instead.")); + }); + }); + describe("#manual track APIs", () => { it("trackDependency http", async () => { const telemetry: DependencyTelemetry = { @@ -213,6 +281,27 @@ describe("shim/TelemetryClient", () => { assert.equal(testMetrics.scopeMetrics[0].metrics[0].dataPoints[0].value.sum, 100); }); + it("trackMetric should handle errors gracefully", async () => { + const telemetry = { + name: "ErrorMetric", + value: 50, + }; + + // Force an error by stubbing metrics.getMeterProvider().getMeter() + const error = new Error("Failed to get meter"); + const getMeterStub = sandbox.stub(metrics.getMeterProvider(), 'getMeter').throws(error); + + // This should now throw an error internally, but the method should catch it + client.trackMetric(telemetry); + + // Verify the error was logged + assert.ok(diagErrorStub.calledOnce); + assert.ok(diagErrorStub.calledWith(`Failed to record metric: ${error}`)); + + // Restore the stub + getMeterStub.restore(); + }); + it("trackAvailability", async () => { const stub = sandbox.stub(logProcessor, "onEmit"); const telemetry = { From b3ec9eece3e8f88b5b434d0cac27c49d168b57e5 Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 18:52:49 -0700 Subject: [PATCH 08/10] Clean up tests. --- test/unitTests/shared/util.tests.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/unitTests/shared/util.tests.ts b/test/unitTests/shared/util.tests.ts index c91dcc006..44dea8fed 100644 --- a/test/unitTests/shared/util.tests.ts +++ b/test/unitTests/shared/util.tests.ts @@ -340,23 +340,6 @@ describe("Library/Util", () => { assert.strictEqual(err.message, "ReaddirSync error"); } }); - - // it("should handle errors in statSync", () => { - // const readdirSyncStub = sandbox.stub(fs, "readdirSync"); - // // Create mock file entries - // const mockFiles = ["file1.txt", "file2.txt"]; - // readdirSyncStub.returns(mockFiles); - - // const statSyncStub = sandbox.stub(fs, "statSync"); - // statSyncStub.throws(new Error("StatSync error")); - - // try { - // fileSystemHelper.getShallowDirectorySizeSync("test-dir"); - // assert.fail("Should have thrown an error"); - // } catch (err) { - // assert.strictEqual(err.message, "StatSync error"); - // } - // }); }); describe("#getShallowFileSize()", () => { From 153b5492ff77c587c1b368f59707d07e9b0acc7f Mon Sep 17 00:00:00 2001 From: Jackson Weber Date: Wed, 21 May 2025 18:55:17 -0700 Subject: [PATCH 09/10] Clean up fs tests. --- test/unitTests/shared/util.tests.ts | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/test/unitTests/shared/util.tests.ts b/test/unitTests/shared/util.tests.ts index 44dea8fed..583cc026f 100644 --- a/test/unitTests/shared/util.tests.ts +++ b/test/unitTests/shared/util.tests.ts @@ -303,32 +303,6 @@ describe("Library/Util", () => { assert.strictEqual(size, 0); }); - it("should only count files at root level and ignore subdirectories", () => { - // Create mixed content directory - const mixedSyncDir = path.join(tempDir, "mixed-sync-dir"); - if (!fs.existsSync(mixedSyncDir)) { - fs.mkdirSync(mixedSyncDir, { recursive: true }); - } - - // Create files in the root - const file1 = path.join(mixedSyncDir, "file1.txt"); - const file2 = path.join(mixedSyncDir, "file2.txt"); - fs.writeFileSync(file1, "file1-sync-content"); - fs.writeFileSync(file2, "file2-sync-content"); - - // Create subdirectory with files - const subDir = path.join(mixedSyncDir, "subdir"); - if (!fs.existsSync(subDir)) { - fs.mkdirSync(subDir, { recursive: true }); - } - const subFile = path.join(subDir, "subfile.txt"); - fs.writeFileSync(subFile, "subfile-sync-content"); - - const size = fileSystemHelper.getShallowDirectorySizeSync(mixedSyncDir); - assert.strictEqual(size, "file1-sync-content".length + "file2-sync-content".length, - "Should only include size of root level files"); - }); - it("should handle errors in readdirSync", () => { const readdirSyncStub = sandbox.stub(fs, "readdirSync"); readdirSyncStub.throws(new Error("ReaddirSync error")); From f97a062a1d31a3b36e193d06b8ff44f93611574a Mon Sep 17 00:00:00 2001 From: Jackson Weber <47067795+JacksonWeber@users.noreply.github.com> Date: Wed, 21 May 2025 19:02:51 -0700 Subject: [PATCH 10/10] Update test/unitTests/shim/telemetryClient.tests.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/unitTests/shim/telemetryClient.tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unitTests/shim/telemetryClient.tests.ts b/test/unitTests/shim/telemetryClient.tests.ts index 064067c1d..c12486be1 100644 --- a/test/unitTests/shim/telemetryClient.tests.ts +++ b/test/unitTests/shim/telemetryClient.tests.ts @@ -5,7 +5,7 @@ import * as nock from "nock"; import * as sinon from "sinon"; import { Context, ProxyTracerProvider, trace, metrics, diag } from "@opentelemetry/api"; import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base"; -import { DependencyTelemetry, RequestTelemetry, TelemetryType } from "../../../src/declarations/contracts"; +import { DependencyTelemetry, RequestTelemetry } from "../../../src/declarations/contracts"; import { TelemetryClient } from "../../../src/shim/telemetryClient"; import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"; import { AzureMonitorExporterOptions, AzureMonitorMetricExporter } from "@azure/monitor-opentelemetry-exporter";