diff --git a/HandlePubKeyRevoke/__tests__/handler.test.ts b/HandlePubKeyRevoke/__tests__/handler.test.ts index 9cf32b4..2c77568 100644 --- a/HandlePubKeyRevoke/__tests__/handler.test.ts +++ b/HandlePubKeyRevoke/__tests__/handler.test.ts @@ -96,8 +96,10 @@ describe("handleRevoke", () => { expect(mockAppinsights.trackException).toHaveBeenCalled(); expect(mockAppinsights.trackException).toHaveBeenCalledWith( expect.objectContaining({ + tagOverrides: { samplingEnabled: "false" }, properties: expect.objectContaining({ detail: "PERMANENT", + assertionRef: "unknown", name: "lollipop.pubKeys.revoke.failure", retryCount: "1", maxRetryCount: "5" @@ -128,11 +130,13 @@ describe("handleRevoke", () => { expect(mockAppinsights.trackException).toHaveBeenCalled(); expect(mockAppinsights.trackException).toHaveBeenCalledWith( expect.objectContaining({ + tagOverrides: { samplingEnabled: "true" }, properties: expect.objectContaining({ detail: "TRANSIENT", fatal: "false", isSuccess: "false", modelId: "", + assertionRef: aValidAssertionRef, name: "lollipop.pubKeys.revoke.failure", retryCount: "1", maxRetryCount: "5" @@ -145,6 +149,47 @@ describe("handleRevoke", () => { ]); }); + it("GIVEN a valid revoke message WHEN findLastVersion fails the last retry THEN it should throw with a transient failure without sampling", async () => { + findLastVersionByModelIdMock.mockImplementationOnce(() => + TE.left(toCosmosErrorResponse("Cannot reach cosmosDB")) + ); + await expect( + handleRevoke( + { + ...contextMock, + executionContext: { + retryContext: { retryCount: 4, maxRetryCount: 5 } + } + }, + mockAppinsights as any, + lollipopKeysModelMock, + masterAlgo, + aValidRevokeInput + ) + ).rejects.toBeDefined(); + + expect(mockAppinsights.trackException).toHaveBeenCalled(); + expect(mockAppinsights.trackException).toHaveBeenCalledWith( + expect.objectContaining({ + tagOverrides: { samplingEnabled: "false" }, + properties: expect.objectContaining({ + detail: "TRANSIENT", + fatal: "false", + isSuccess: "false", + modelId: "", + assertionRef: aValidAssertionRef, + name: "lollipop.pubKeys.revoke.failure", + retryCount: "4", + maxRetryCount: "5" + }) + }) + ); + expect(findLastVersionByModelIdMock).toHaveBeenCalledTimes(1); + expect(findLastVersionByModelIdMock).toHaveBeenCalledWith([ + aValidRevokeInput.assertion_ref + ]); + }); + it("GIVEN a valid revoke message WHEN findLastVersion returns none THEN it should success without perform any upsert", async () => { findLastVersionByModelIdMock.mockImplementationOnce(() => TE.right(O.none)); const result = await handleRevoke( @@ -243,8 +288,10 @@ describe("handleRevoke", () => { expect(mockAppinsights.trackException).toHaveBeenCalled(); expect(mockAppinsights.trackException).toHaveBeenCalledWith( expect.objectContaining({ + tagOverrides: { samplingEnabled: "false" }, properties: expect.objectContaining({ detail: "PERMANENT", + assertionRef: aValidAssertionRef, name: "lollipop.pubKeys.revoke.failure", retryCount: "1", maxRetryCount: "5" @@ -285,8 +332,10 @@ describe("handleRevoke", () => { expect(mockAppinsights.trackException).toHaveBeenCalled(); expect(mockAppinsights.trackException).toHaveBeenCalledWith( expect.objectContaining({ + tagOverrides: { samplingEnabled: "false" }, properties: expect.objectContaining({ detail: "PERMANENT", + assertionRef: aValidAssertionRef, name: "lollipop.pubKeys.revoke.failure", retryCount: "1", maxRetryCount: "5" @@ -331,8 +380,10 @@ describe("handleRevoke", () => { expect(mockAppinsights.trackException).toHaveBeenCalled(); expect(mockAppinsights.trackException).toHaveBeenCalledWith( expect.objectContaining({ + tagOverrides: { samplingEnabled: "true" }, properties: expect.objectContaining({ detail: "TRANSIENT", + assertionRef: aValidAssertionRef, name: "lollipop.pubKeys.revoke.failure", retryCount: "1", maxRetryCount: "5" @@ -371,10 +422,12 @@ describe("handleRevoke", () => { expect(mockAppinsights.trackException).toHaveBeenCalled(); expect(mockAppinsights.trackException).toHaveBeenCalledWith( expect.objectContaining({ + tagOverrides: { samplingEnabled: "true" }, properties: expect.objectContaining({ detail: "TRANSIENT", name: "lollipop.pubKeys.revoke.failure", retryCount: "1", + assertionRef: aValidAssertionRef, maxRetryCount: "5", errorMessage: expect.stringContaining( "Cannot find a master lollipopPubKey" @@ -414,8 +467,10 @@ describe("handleRevoke", () => { expect(mockAppinsights.trackException).toHaveBeenCalled(); expect(mockAppinsights.trackException).toHaveBeenCalledWith( expect.objectContaining({ + tagOverrides: { samplingEnabled: "true" }, properties: expect.objectContaining({ detail: "TRANSIENT", + assertionRef: aValidAssertionRef, errorMessage: expect.stringContaining( "Cannot decode a VALID master lollipopPubKey" ), diff --git a/HandlePubKeyRevoke/handler.ts b/HandlePubKeyRevoke/handler.ts index 748b210..4c105d1 100644 --- a/HandlePubKeyRevoke/handler.ts +++ b/HandlePubKeyRevoke/handler.ts @@ -100,6 +100,15 @@ const extractPubKeysToRevoke = ( ) ); +/** + * + * @param context The function context + * @returns `true` if retryCount >= maxRetryCount-1, `false` otherwise + */ +const isLastRetry = (context: Context): boolean => + (context.executionContext.retryContext?.retryCount ?? 0) >= + (context.executionContext.retryContext?.maxRetryCount ?? 0) - 1; + const revokePubKey = (lollipopKeysModel: LolliPOPKeysModel) => ( notPendingLollipopPubKey: NotPendingLolliPopPubKeys ): TE.TaskEither => @@ -164,6 +173,12 @@ export const handleRevoke = ( trackException(telemetryClient, { exception: new Error(error), properties: { + assertionRef: pipe( + rawRevokeMessage, + RevokeAssertionRefInfo.decode, + E.map(message => message.assertion_ref), + E.getOrElse(() => "unknown") + ), detail: err.kind, errorMessage: error, fatal: PermanentFailure.is(err).toString(), @@ -177,7 +192,10 @@ export const handleRevoke = ( context.executionContext.retryContext?.retryCount ?? "undefined" ) }, - tagOverrides: { samplingEnabled: "false" } + tagOverrides: { + samplingEnabled: + !isTransient || isLastRetry(context) ? "false" : "true" + } }); context.log.error(error); if (isTransient) {