import * as sinon from "sinon";
import { expect } from "chai";
import { BentleyError, IModelStatus } from "@itwin/core-bentley";
import { IpcHandler, IpcHost } from "../IpcHost";
class OuterError extends Error {
    originalError;
    context;
    errors;
    constructor(originalError) {
        super("outer");
        this.originalError = originalError;
        this.context = { cause: originalError };
        this.errors = [new Error("array-child-1"), new Error("array-child-2")];
    }
}
class MockIpcHandler extends IpcHandler {
    get channelName() { return "mock-channel"; }
    mockMethod() {
        return "mock-value";
    }
    throwNestedError() {
        const inner = new Error("inner-message");
        throw new OuterError(inner);
    }
    throwCircularError() {
        const err = new Error("circular");
        err.cause = err; // circular reference
        throw err;
    }
    throwErrorWithNativeCause() {
        throw new Error("native-cause-outer", { cause: new Error("native-cause-inner") });
    }
    throwBentleyErrorWithFunctionMetaData() {
        throw new BentleyError(IModelStatus.BadArg, "bentley-fn-meta", () => ({ detail: "computed" }));
    }
    throwBentleyErrorWithObjectMetaData() {
        throw new BentleyError(IModelStatus.BadArg, "bentley-obj-meta", { detail: "static" });
    }
    throwNestedBentleyErrorWithFunctionMetaData() {
        const inner = new BentleyError(IModelStatus.BadArg, "inner-bentley", () => ({ detail: "nested-computed" }));
        const outer = new Error("outer");
        outer.cause = inner;
        throw outer;
    }
    throwErrorWithFunctionProperty() {
        const err = new Error("fn-prop");
        err.retry = () => { };
        throw err;
    }
    throwErrorWithSymbolProperty() {
        const err = new Error("sym-prop");
        err[Symbol("tag")] = "should-be-dropped";
        throw err;
    }
    throwErrorWithDateProperty() {
        const err = new Error("date-prop");
        err.timestamp = new Date("2024-01-01");
        throw err;
    }
    throwErrorWithClassInstanceProperty() {
        const err = new Error("class-instance-prop");
        err.request = new URL("https://example.com"); // arbitrary non-plain class instance
        throw err;
    }
    throwErrorWithMixedArray() {
        const err = new Error("mixed-array");
        err.items = ["string-value", 42, new Date("2024-01-01"), new URL("https://example.com"), new Error("array-error")];
        throw err;
    }
    #privateFunction() { }
}
describe("IpcHost", () => {
    let socket;
    beforeEach(async () => {
        socket = {
            send: sinon.stub(),
            addListener: sinon.stub(),
            removeListener: sinon.stub(),
            handle: sinon.stub(),
        };
        await IpcHost.startup({ ipcHost: { socket } });
    });
    describe("IpcHandler", () => {
        let handler;
        beforeEach(() => {
            MockIpcHandler.register();
            const handleCall = socket.handle.getCalls().find((call) => call.args[0] === "itwin.mock-channel");
            expect(handleCall).to.not.be.undefined;
            handler = handleCall.args[1];
        });
        it("should call public methods", async () => {
            const ipcReturn = await handler(undefined, "mockMethod");
            expect(ipcReturn.result).to.equal("mock-value");
            expect(ipcReturn.error).to.be.undefined;
        });
        it("should not call private methods", async () => {
            const ipcReturn = await handler(undefined, "#privateFunction");
            expect(ipcReturn.result).to.be.undefined;
            expect(ipcReturn.error).to.not.be.undefined;
        });
        it("should not call methods inherited from Object", async () => {
            const ipcReturn = await handler(undefined, "toString");
            expect(ipcReturn.result).to.be.undefined;
            expect(ipcReturn.error).to.not.be.undefined;
        });
        it("should serialize nested Error properties preserving message and stack", async () => {
            const ipcReturn = await handler(undefined, "throwNestedError");
            const error = ipcReturn.error;
            // Top-level error
            expect(error.message).to.equal("outer");
            expect(error.stack).to.be.a("string");
            // Direct Error property
            expect(error.originalError.message).to.equal("inner-message");
            expect(error.originalError.stack).to.be.a("string");
            // Error nested inside a plain-object property
            expect(error.context.cause.message).to.equal("inner-message");
            expect(error.context.cause.stack).to.be.a("string");
            // Array of Errors stays an array
            expect(Array.isArray(error.errors)).to.be.true;
            expect(error.errors[0].message).to.equal("array-child-1");
            expect(error.errors[1].message).to.equal("array-child-2");
        });
        it("should omit stack on nested Errors when IpcHost.noStack is set", async () => {
            const originalNoStack = IpcHost.noStack;
            IpcHost.noStack = true;
            try {
                const ipcReturn = await handler(undefined, "throwNestedError");
                const error = ipcReturn.error;
                expect(error.stack).to.be.undefined;
                expect(error.originalError.stack).to.be.undefined;
                expect(error.context.cause.stack).to.be.undefined;
                expect(error.errors[0].stack).to.be.undefined;
                expect(error.errors[1].stack).to.be.undefined;
            }
            finally {
                IpcHost.noStack = originalNoStack;
            }
        });
        it("should not infinitely recurse on circular Error references", async () => {
            const ipcReturn = await handler(undefined, "throwCircularError");
            const error = ipcReturn.error;
            expect(error.message).to.equal("circular");
            expect(error.cause).to.be.undefined;
        });
        it("should serialize native Error.cause even though it is non-enumerable", async () => {
            const ipcReturn = await handler(undefined, "throwErrorWithNativeCause");
            const error = ipcReturn.error;
            expect(error.message).to.equal("native-cause-outer");
            expect(error.cause).to.not.be.undefined;
            expect(error.cause.message).to.equal("native-cause-inner");
            expect(error.cause.stack).to.be.a("string");
        });
        it("should drop _metaData and expose loggingMetadata when BentleyError has a GetMetaDataFunction", async () => {
            const ipcReturn = await handler(undefined, "throwBentleyErrorWithFunctionMetaData");
            const error = ipcReturn.error;
            expect(error.message).to.equal("bentley-fn-meta");
            // _metaData is a function — must not survive serialization
            expect(error._metaData).to.be.undefined;
            // loggingMetadata should be the resolved object
            expect(error.loggingMetadata).to.deep.equal({ detail: "computed" });
        });
        it("should drop _metaData and expose loggingMetadata when BentleyError has an object metaData", async () => {
            const ipcReturn = await handler(undefined, "throwBentleyErrorWithObjectMetaData");
            const error = ipcReturn.error;
            expect(error.message).to.equal("bentley-obj-meta");
            expect(error._metaData).to.be.undefined;
            expect(error.loggingMetadata).to.deep.equal({ detail: "static" });
        });
        it("should drop function-valued _metaData on a nested BentleyError and still expose loggingMetadata", async () => {
            const ipcReturn = await handler(undefined, "throwNestedBentleyErrorWithFunctionMetaData");
            const error = ipcReturn.error;
            expect(error.message).to.equal("outer");
            // The nested BentleyError inside .cause must also have _metaData stripped and loggingMetadata populated
            expect(error.cause).to.not.be.undefined;
            expect(error.cause.message).to.equal("inner-bentley");
            expect(error.cause._metaData).to.be.undefined;
            expect(error.cause.loggingMetadata).to.deep.equal({ detail: "nested-computed" });
        });
        it("should drop arbitrary function-valued properties from a thrown Error", async () => {
            const ipcReturn = await handler(undefined, "throwErrorWithFunctionProperty");
            const error = ipcReturn.error;
            expect(error.message).to.equal("fn-prop");
            expect(error.retry).to.be.undefined;
        });
        it("should not include symbol-keyed properties from a thrown Error", async () => {
            const ipcReturn = await handler(undefined, "throwErrorWithSymbolProperty");
            const error = ipcReturn.error;
            expect(error.message).to.equal("sym-prop");
            // { ...e } copies enumerable own symbol-keyed properties too.
            // The serializer explicitly strips them (they cannot be structured-cloned).
            expect(Object.getOwnPropertySymbols(error)).to.have.length(0);
        });
        it("should preserve Date properties", async () => {
            const ipcReturn = await handler(undefined, "throwErrorWithDateProperty");
            const error = ipcReturn.error;
            expect(error.message).to.equal("date-prop");
            expect(error.timestamp).to.deep.equal(new Date("2024-01-01"));
        });
        it("should drop non-plain class instance properties", async () => {
            const ipcReturn = await handler(undefined, "throwErrorWithClassInstanceProperty");
            const error = ipcReturn.error;
            expect(error.message).to.equal("class-instance-prop");
            expect(error.request).to.be.undefined;
        });
        it("should sanitize mixed arrays: preserve primitives and Dates, drop non-plain class instances, recurse into Errors", async () => {
            const ipcReturn = await handler(undefined, "throwErrorWithMixedArray");
            const error = ipcReturn.error;
            expect(error.message).to.equal("mixed-array");
            expect(Array.isArray(error.items)).to.be.true;
            expect(error.items[0]).to.equal("string-value"); // primitive: preserved
            expect(error.items[1]).to.equal(42); // primitive: preserved
            expect(error.items[2]).to.deep.equal(new Date("2024-01-01")); // Date: preserved
            expect(error.items[3]).to.be.undefined; // class instance: dropped (→ undefined)
            expect(error.items[4].message).to.equal("array-error"); // Error: recursed
        });
    });
});
//# sourceMappingURL=IpcHost.test.js.map