From 1b44a39170a08d9decb7a53ef02e417e6fc49219 Mon Sep 17 00:00:00 2001 From: anilkaracay <70052819+anilkaracay@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:17:01 +0300 Subject: [PATCH] fix(spend-permission): await wrapped async util in withTelemetry --- .../spend-permission/withTelemetry.test.ts | 113 ++++++++++++++++++ .../spend-permission/withTelemetry.ts | 4 +- 2 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 packages/account-sdk/src/interface/public-utilities/spend-permission/withTelemetry.test.ts diff --git a/packages/account-sdk/src/interface/public-utilities/spend-permission/withTelemetry.test.ts b/packages/account-sdk/src/interface/public-utilities/spend-permission/withTelemetry.test.ts new file mode 100644 index 00000000..9f443eef --- /dev/null +++ b/packages/account-sdk/src/interface/public-utilities/spend-permission/withTelemetry.test.ts @@ -0,0 +1,113 @@ +import { + logSpendPermissionUtilCompleted, + logSpendPermissionUtilError, + logSpendPermissionUtilStarted, +} from ':core/telemetry/events/spend-permission.js'; +import { store } from ':store/store.js'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { withTelemetry } from './withTelemetry.js'; + +// Mock telemetry events (house pattern) +vi.mock(':core/telemetry/events/spend-permission.js', () => ({ + logSpendPermissionUtilStarted: vi.fn(), + logSpendPermissionUtilCompleted: vi.fn(), + logSpendPermissionUtilError: vi.fn(), +})); + +function createDeferred() { + let resolve!: (value: T) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + +describe('withTelemetry', () => { + beforeEach(() => { + vi.clearAllMocks(); + // Ensure telemetry is enabled (wrapper active). The flag is read at + // withTelemetry(fn) call time, so reset it before each test. + store.config.set({ preference: undefined }); + }); + + it('logs Completed only AFTER the wrapped async fn resolves', async () => { + const deferred = createDeferred(); + const fn = vi.fn(() => deferred.promise); + + const wrapped = withTelemetry(fn); + const resultPromise = wrapped(); + + // Started fired synchronously; Completed must NOT fire until the promise resolves. + expect(logSpendPermissionUtilStarted).toHaveBeenCalledTimes(1); + expect(logSpendPermissionUtilCompleted).not.toHaveBeenCalled(); + expect(logSpendPermissionUtilError).not.toHaveBeenCalled(); + + deferred.resolve('done'); + await resultPromise; + + expect(logSpendPermissionUtilCompleted).toHaveBeenCalledTimes(1); + expect(logSpendPermissionUtilError).not.toHaveBeenCalled(); + }); + + it('logs Error when the wrapped async fn rejects', async () => { + const fetchPermissionFn = async () => { + throw new Error('boom'); + }; + + const wrapped = withTelemetry(fetchPermissionFn); + + await expect(wrapped()).rejects.toThrow('boom'); + + expect(logSpendPermissionUtilError).toHaveBeenCalledTimes(1); + expect(logSpendPermissionUtilError).toHaveBeenCalledWith('fetchPermission', 'boom'); + expect(logSpendPermissionUtilCompleted).not.toHaveBeenCalled(); + }); + + it('logs Started before completion', async () => { + const deferred = createDeferred(); + const fn = vi.fn(() => deferred.promise); + + const wrapped = withTelemetry(fn); + const resultPromise = wrapped(); + + expect(logSpendPermissionUtilStarted).toHaveBeenCalledTimes(1); + + deferred.resolve('done'); + await resultPromise; + }); + + it('passes through and logs nothing when telemetry is disabled', async () => { + store.config.set({ preference: { telemetry: false } }); + + const fn = vi.fn(async () => 'value'); + const wrapped = withTelemetry(fn); + + // Passthrough: the raw fn is returned unchanged. + expect(wrapped).toBe(fn); + + await wrapped(); + + expect(logSpendPermissionUtilStarted).not.toHaveBeenCalled(); + expect(logSpendPermissionUtilCompleted).not.toHaveBeenCalled(); + expect(logSpendPermissionUtilError).not.toHaveBeenCalled(); + }); + + it('propagates the resolved value unchanged', async () => { + const fn = async () => 'the-value'; + const wrapped = withTelemetry(fn); + + await expect(wrapped()).resolves.toBe('the-value'); + }); + + it('derives the function name (strips the Fn suffix)', async () => { + const fetchPermissionFn = async () => 'ok'; + const wrapped = withTelemetry(fetchPermissionFn); + + await wrapped(); + + expect(logSpendPermissionUtilStarted).toHaveBeenCalledWith('fetchPermission'); + expect(logSpendPermissionUtilCompleted).toHaveBeenCalledWith('fetchPermission'); + }); +}); diff --git a/packages/account-sdk/src/interface/public-utilities/spend-permission/withTelemetry.ts b/packages/account-sdk/src/interface/public-utilities/spend-permission/withTelemetry.ts index fa140695..2e16b929 100644 --- a/packages/account-sdk/src/interface/public-utilities/spend-permission/withTelemetry.ts +++ b/packages/account-sdk/src/interface/public-utilities/spend-permission/withTelemetry.ts @@ -15,11 +15,11 @@ export function withTelemetry any>(fn: T) { return fn; } - return (...args: Parameters): ReturnType => { + return async (...args: Parameters): Promise>> => { const functionName = getFunctionName(fn); logSpendPermissionUtilStarted(functionName); try { - const result = fn(...args); + const result = await fn(...args); logSpendPermissionUtilCompleted(functionName); return result; } catch (error) {