diff --git a/src/common/utils/with-orphan-promise/with-orphan-promise.test.ts b/src/common/utils/with-orphan-promise/with-orphan-promise.test.ts new file mode 100644 index 0000000000..f20331497c --- /dev/null +++ b/src/common/utils/with-orphan-promise/with-orphan-promise.test.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { AsyncFnMock } from "@async-fn/jest"; +import asyncFn from "@async-fn/jest"; +import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting"; +import loggerInjectable from "../../logger.injectable"; +import type { Logger } from "../../logger"; +import withOrphanPromiseInjectable from "./with-orphan-promise"; + +describe("with orphan promise, when called", () => { + let toBeDecorated: AsyncFnMock<(arg1: string, arg2: string) => Promise>; + let actual: void; + let loggerStub: Logger; + + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + di.register(withOrphanPromiseInjectable); + + loggerStub = { error: jest.fn() } as unknown as Logger; + + di.override(loggerInjectable, () => loggerStub); + + const withOrphanPromise = di.inject(withOrphanPromiseInjectable); + + toBeDecorated = asyncFn(); + + const decorated = withOrphanPromise(toBeDecorated); + + actual = decorated("some-argument", "some-other-argument"); + }); + + it("calls decorated with arguments", () => { + expect(toBeDecorated).toHaveBeenCalledWith("some-argument", "some-other-argument"); + }); + + it("given promise returned by decorated has not been fulfilled yet, already returns nothing", () => { + expect(actual).toBeUndefined(); + }); + + it("when decorated function resolves, nothing happens", async () => { + await toBeDecorated.resolve("irrelevant"); + // Note: there is no expect, test is here only for documentation. + }); + + describe("when decorated function rejects", () => { + beforeEach(async () => { + await toBeDecorated.reject("some-error"); + }); + + it("logs the rejection", () => { + expect(loggerStub.error).toHaveBeenCalledWith("Orphan promise rejection encountered", "some-error"); + }); + + it("nothing else happens", () => { + // Note: there is no expect, test is here only for documentation. + }); + }); +}); diff --git a/src/common/utils/with-orphan-promise/with-orphan-promise.ts b/src/common/utils/with-orphan-promise/with-orphan-promise.ts new file mode 100644 index 0000000000..42e6cb9a61 --- /dev/null +++ b/src/common/utils/with-orphan-promise/with-orphan-promise.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import withErrorLoggingInjectable from "../with-error-logging/with-error-logging.injectable"; +import { withErrorSuppression } from "../with-error-suppression/with-error-suppression"; +import { pipeline } from "@ogre-tools/fp"; + +const withOrphanPromiseInjectable = getInjectable({ + id: "with-orphan-promise", + + instantiate: (di) => { + const withErrorLoggingFor = di.inject(withErrorLoggingInjectable); + + return Promise>(toBeDecorated: T) => + (...args: Parameters): void => { + const decorated = pipeline( + toBeDecorated, + withErrorLoggingFor(() => "Orphan promise rejection encountered"), + withErrorSuppression, + ); + + decorated(...args); + }; + }, +}); + +export default withOrphanPromiseInjectable;