diff --git a/lambdas/authorizer/src/__tests__/index.test.ts b/lambdas/authorizer/src/__tests__/index.test.ts index 4020b55e..58041e86 100644 --- a/lambdas/authorizer/src/__tests__/index.test.ts +++ b/lambdas/authorizer/src/__tests__/index.test.ts @@ -4,11 +4,30 @@ import { Callback, Context, } from "aws-lambda"; +import { metricScope } from "aws-embedded-metrics"; import pino from "pino"; import { Deps } from "../deps"; import { EnvVars } from "../env"; import createAuthorizerHandler from "../authorizer"; +jest.mock("aws-embedded-metrics", () => { + const metricsMock = { + setNamespace: jest.fn(), + putMetric: jest.fn(), + }; + + return { + metricScope: jest.fn((handler: (metrics: typeof metricsMock) => unknown) => { + const wrapped = handler(metricsMock); + if (typeof wrapped === "function") { + return wrapped(); + } + return undefined; + }), + __metricsMock: metricsMock, + }; +}); + const mockedDeps: jest.Mocked = { logger: { info: jest.fn(), @@ -60,6 +79,17 @@ describe("Authorizer Lambda Function", () => { jest .useFakeTimers({ doNotFake: ["nextTick"] }) .setSystemTime(new Date("2025-11-03T14:19:00Z")); + (metricScope as jest.Mock).mockClear(); + const metricsMock = (jest.requireMock( + "aws-embedded-metrics", + ) as { + __metricsMock: { + setNamespace: jest.Mock; + putMetric: jest.Mock; + }; + }).__metricsMock; + metricsMock.setNamespace.mockClear(); + metricsMock.putMetric.mockClear(); }); afterEach(() => { @@ -73,10 +103,7 @@ describe("Authorizer Lambda Function", () => { handler(mockEvent, mockContext, mockCallback); await new Promise(process.nextTick); - const mockedInfo = mockedDeps.logger.info as jest.Mock; - expect(mockedInfo.mock.calls).not.toContainEqual( - expect.stringContaining("CloudWatchMetrics"), - ); + expect(metricScope).not.toHaveBeenCalled(); }); it("Should log CloudWatch metric when the certificate expiry threshold is reached", async () => { @@ -88,29 +115,19 @@ describe("Authorizer Lambda Function", () => { handler(mockEvent, mockContext, mockCallback); await new Promise(process.nextTick); - const mockedInfo = mockedDeps.logger.info as jest.Mock; - expect(mockedInfo.mock.calls.map((call) => call[0])).toContain( - JSON.stringify({ - _aws: { - Timestamp: 1_762_179_540_000, - CloudWatchMetrics: [ - { - Namespace: "cloudwatch-namespace", - Dimensions: ["SUBJECT_DN", "NOT_AFTER"], - Metrics: [ - { - Name: "apim-client-certificate-near-expiry", - Unit: "Count", - Value: 1, - }, - ], - }, - ], - }, - SUBJECT_DN: "CN=test-subject", - NOT_AFTER: "2025-11-17T14:19:00Z", - "apim-client-certificate-near-expiry": 1, - }), + const metricsMock = (jest.requireMock("aws-embedded-metrics") as { + __metricsMock: { + setNamespace: jest.Mock; + putMetric: jest.Mock; + }; + }).__metricsMock; + + expect(metricScope).toHaveBeenCalledTimes(1); + expect(metricsMock.setNamespace).toHaveBeenCalledWith("authorizer"); + expect(metricsMock.putMetric).toHaveBeenCalledWith( + "apim-client-certificate-near-expiry", + 14, + "Count", ); }); @@ -123,10 +140,7 @@ describe("Authorizer Lambda Function", () => { handler(mockEvent, mockContext, mockCallback); await new Promise(process.nextTick); - const mockedInfo = mockedDeps.logger.info as jest.Mock; - expect(mockedInfo.mock.calls).not.toContainEqual( - expect.stringContaining("CloudWatchMetrics"), - ); + expect(metricScope).not.toHaveBeenCalled(); }); }); diff --git a/lambdas/authorizer/src/authorizer.ts b/lambdas/authorizer/src/authorizer.ts index d5a0bd55..0f7c5eb8 100644 --- a/lambdas/authorizer/src/authorizer.ts +++ b/lambdas/authorizer/src/authorizer.ts @@ -6,7 +6,8 @@ import { APIGatewayRequestAuthorizerHandler, Callback, Context, -} from "aws-lambda"; +} from "aws-lambda" +import { MetricsLogger, metricScope } from "aws-embedded-metrics"; import { Supplier } from "@internal/datastore"; import { Deps } from "./deps"; @@ -100,33 +101,6 @@ function getCertificateExpiryInDays( return (expiry - now) / (1000 * 60 * 60 * 24); } -function buildCloudWatchMetric( - namespace: string, - certificate: APIGatewayEventClientCertificate, -) { - return { - _aws: { - Timestamp: Date.now(), - CloudWatchMetrics: [ - { - Namespace: namespace, - Dimensions: ["SUBJECT_DN", "NOT_AFTER"], - Metrics: [ - { - Name: "apim-client-certificate-near-expiry", - Unit: "Count", - Value: 1, - }, - ], - }, - ], - }, - SUBJECT_DN: certificate.subjectDN, - NOT_AFTER: certificate.validity.notAfter, - "apim-client-certificate-near-expiry": 1, - }; -} - async function checkCertificateExpiry( certificate: APIGatewayEventClientCertificate | null, deps: Deps, @@ -146,10 +120,11 @@ async function checkCertificateExpiry( const expiry = getCertificateExpiryInDays(certificate); if (expiry <= deps.env.CLIENT_CERTIFICATE_EXPIRATION_ALERT_DAYS) { - deps.logger.info( - JSON.stringify( - buildCloudWatchMetric(deps.env.CLOUDWATCH_NAMESPACE, certificate), - ), - ); - } + metricScope( + (metrics: MetricsLogger) => async () => { + deps.logger.warn(`APIM Certificated expiry in ${expiry} days`); + metrics.setNamespace(process.env.AWS_LAMBDA_FUNCTION_NAME || 'authorizer'); + metrics.putMetric("apim-client-certificate-near-expiry", expiry, "Count"); + }); + }; }