diff --git a/layers/jest.config.js b/layers/jest.config.js deleted file mode 100644 index 6199d00704..0000000000 --- a/layers/jest.config.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - displayName: { - name: 'Powertools for AWS Lambda (TypeScript) utility: LAYERS', - color: 'black', - }, - runner: 'groups', - preset: 'ts-jest', - transform: { - '^.+\\.ts?$': 'ts-jest', - }, - moduleFileExtensions: ['js', 'ts'], - collectCoverageFrom: ['**/src/**/*.ts', '!**/node_modules/**'], - testMatch: ['**/?(*.)+(spec|test).ts'], - roots: ['/src', '/tests'], - testPathIgnorePatterns: ['/node_modules/'], - testEnvironment: 'node', - coveragePathIgnorePatterns: ['/node_modules/', '/types/'], - coverageThreshold: { - global: { - statements: 100, - branches: 100, - functions: 100, - lines: 100, - }, - }, - coverageReporters: ['json-summary', 'text', 'lcov'], - setupFiles: ['/tests/helpers/populateEnvironmentVariables.ts'], -}; diff --git a/layers/package.json b/layers/package.json index 34b1f289dd..f3e33810df 100644 --- a/layers/package.json +++ b/layers/package.json @@ -7,15 +7,19 @@ "private": true, "description": "This CDK app is meant to be used to publish Powertools for AWS Lambda (TypeScript) Lambda Layer. It is composed of a single stack deploying the Layer into the target account.", "scripts": { + "test": "vitest --run tests/unit", + "test:unit": "vitest --run tests/unit", + "test:unit:coverage": "echo 'Not Implemented'", + "test:unit:types": "echo 'Not Implemented'", + "test:e2e:nodejs18x": "echo 'Not Implemented'", + "test:e2e:nodejs20x": "echo 'Not Implemented'", + "test:e2e": "vitest --run tests/e2e", "build": "echo 'Not applicable, run `npx cdk synth` instead to build the stack'", - "test": "echo 'Not applicable'", "jest": "jest --detectOpenHandles", "cdk": "cdk", "package": "echo 'Not applicable'", "lint": "biome lint .", "lint:fix": "biome check --write .", - "test:unit": "jest --group=unit", - "test:e2e": "jest --group=e2e", "createLayerFolder": "cdk synth --context BuildFromLocal=true" }, "repository": { diff --git a/layers/tests/e2e/layerPublisher.test.ts b/layers/tests/e2e/layerPublisher.test.ts index 87261d7c91..3cb7697031 100644 --- a/layers/tests/e2e/layerPublisher.test.ts +++ b/layers/tests/e2e/layerPublisher.test.ts @@ -1,8 +1,3 @@ -/** - * Test LayerPublisherStack class - * - * @group e2e/layers/all - */ import { join } from 'node:path'; import { TestInvocationLogs, @@ -13,23 +8,14 @@ import { import { TestNodejsFunction } from '@aws-lambda-powertools/testing-utils/resources/lambda'; import { App } from 'aws-cdk-lib'; import { LayerVersion } from 'aws-cdk-lib/aws-lambda'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import packageJson from '../../package.json'; -import { LayerPublisherStack } from '../../src/layer-publisher-stack'; +import { LayerPublisherStack } from '../../src/layer-publisher-stack.js'; import { RESOURCE_NAME_PREFIX, SETUP_TIMEOUT, TEARDOWN_TIMEOUT, -} from './constants'; - -jest.spyOn(console, 'log').mockImplementation(); - -function assertLogs( - logs: TestInvocationLogs | undefined -): asserts logs is TestInvocationLogs { - if (!logs) { - throw new Error('Function logs are not available'); - } -} +} from './constants.js'; /** * This test has two stacks: @@ -137,70 +123,78 @@ describe('Layers E2E tests', () => { } }, SETUP_TIMEOUT); - describe.each(cases)( - 'utilities tests for %s output format', + it.each(cases)( + 'imports and instantiates all utilities (%s)', + (outputFormat) => { + const invocationLogs = invocationLogsMap.get( + outputFormat + ) as TestInvocationLogs; + + expect(invocationLogs.doesAnyFunctionLogsContains('ERROR')).toBe(false); + } + ); + + it.each(cases)( + 'emits a warning log for missing Metrics namespace (%s)', (outputFormat) => { - it('should have no errors in the logs, which indicates the pacakges version matches the expected one', () => { - const maybeInvocationLogs = invocationLogsMap.get(outputFormat); - assertLogs(maybeInvocationLogs); - const invocationLogs = maybeInvocationLogs; - const logs = invocationLogs.getFunctionLogs('ERROR'); - - expect(logs.length).toBe(0); - }); - - it('should have one warning related to missing Metrics namespace', () => { - const maybeInvocationLogs = invocationLogsMap.get(outputFormat); - assertLogs(maybeInvocationLogs); - const invocationLogs = maybeInvocationLogs; - const logs = invocationLogs.getFunctionLogs('WARN'); - - expect(logs.length).toBe(1); - expect(logs[0]).toContain('Namespace should be defined, default used'); - }); - - it('should have one info log related to coldstart metric', () => { - const maybeInvocationLogs = invocationLogsMap.get(outputFormat); - assertLogs(maybeInvocationLogs); - const invocationLogs = maybeInvocationLogs; - const logs = invocationLogs.getFunctionLogs(); - - const emfLogEntry = logs.find((log) => - log.match( - /{"_aws":{"Timestamp":\d+,"CloudWatchMetrics":\[\{"Namespace":"\S+","Dimensions":\[\["service"\]\],"Metrics":\[\{"Name":"ColdStart","Unit":"Count"\}\]\}\]},"service":"\S+","ColdStart":1}/ - ) - ); - - expect(emfLogEntry).toBeDefined(); - }); - - it('should have one debug log with tracer subsegment info', () => { - const maybeInvocationLogs = invocationLogsMap.get(outputFormat); - assertLogs(maybeInvocationLogs); - const invocationLogs = maybeInvocationLogs; - const logs = invocationLogs.getFunctionLogs('DEBUG'); - - expect(logs.length).toBe(1); - const logEntry = TestInvocationLogs.parseFunctionLog(logs[0]); - expect(logEntry.message).toContain('subsegment'); - expect(logEntry.subsegment).toBeDefined(); - const subsegment = JSON.parse(logEntry.subsegment as string); - const traceIdFromLog = subsegment.trace_id; - expect(subsegment).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: '### index.handler', - start_time: expect.any(Number), - end_time: expect.any(Number), - type: 'subsegment', - annotations: { - ColdStart: true, - }, - parent_id: expect.any(String), - trace_id: traceIdFromLog, - }) - ); - }); + const invocationLogs = invocationLogsMap.get( + outputFormat + ) as TestInvocationLogs; + const logs = invocationLogs.getFunctionLogs('WARN'); + + expect(logs.length).toBe(1); + expect( + invocationLogs.doesAnyFunctionLogsContains( + /Namespace should be defined, default used/, + 'WARN' + ) + ).toBe(true); + /* expect(logEntry.message).toEqual( + 'Namespace should be defined, default used' + ); */ + } + ); + + it.each(cases)('emits an EMF log (%s)', (outputFormat) => { + const invocationLogs = invocationLogsMap.get( + outputFormat + ) as TestInvocationLogs; + + expect( + invocationLogs.doesAnyFunctionLogsContains( + /{"_aws":{"Timestamp":\d+,"CloudWatchMetrics":\[\{"Namespace":"\S+","Dimensions":\[\["service"\]\],"Metrics":\[\{"Name":"ColdStart","Unit":"Count"\}\]\}\]},"service":"\S+","ColdStart":1}/ + ) + ).toBe(true); + }); + + it.each(cases)( + 'emits a debug log with tracer subsegment info (%s)', + (outputFormat) => { + const invocationLogs = invocationLogsMap.get( + outputFormat + ) as TestInvocationLogs; + const logs = invocationLogs.getFunctionLogs('DEBUG'); + + expect(logs.length).toBe(1); + const logEntry = TestInvocationLogs.parseFunctionLog(logs[0]); + expect(logEntry.message).toContain('subsegment'); + expect(logEntry.subsegment).toBeDefined(); + const subsegment = JSON.parse(logEntry.subsegment as string); + const traceIdFromLog = subsegment.trace_id; + expect(subsegment).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: '### index.handler', + start_time: expect.any(Number), + end_time: expect.any(Number), + type: 'subsegment', + annotations: { + ColdStart: true, + }, + parent_id: expect.any(String), + trace_id: traceIdFromLog, + }) + ); } ); diff --git a/layers/tests/unit/layer-publisher.test.ts b/layers/tests/unit/layer-publisher.test.ts index 092a05ddce..fc20e88d06 100644 --- a/layers/tests/unit/layer-publisher.test.ts +++ b/layers/tests/unit/layer-publisher.test.ts @@ -1,11 +1,6 @@ -/** - * Test LayerPublisherStack class - * - * @group unit/layers/all - */ - import { App } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; +import { describe, it } from 'vitest'; import { LayerPublisherStack } from '../../src/layer-publisher-stack'; describe('Class: LayerPublisherStack', () => { diff --git a/layers/vitest.config.ts b/layers/vitest.config.ts new file mode 100644 index 0000000000..d5aa737c68 --- /dev/null +++ b/layers/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + environment: 'node', + }, +}); diff --git a/packages/testing/src/TestInvocationLogs.ts b/packages/testing/src/TestInvocationLogs.ts index 1ba52b419d..2de21918ae 100644 --- a/packages/testing/src/TestInvocationLogs.ts +++ b/packages/testing/src/TestInvocationLogs.ts @@ -34,17 +34,17 @@ class TestInvocationLogs { } /** - * Find all functional logs whether it contains a given text - * @param text - * @param levelToFilter level to filter - * @returns + * Test whether any of the function logs contain the provided text or regex. + * + * @param needle - text or regex to search for in the logs + * @param levelToFilter - level to filter */ public doesAnyFunctionLogsContains( - text: string, + needle: string | RegExp, levelToFilter?: keyof typeof LogLevel ): boolean { const filteredLogs = this.getFunctionLogs(levelToFilter).filter((log) => - log.includes(text) + typeof needle === 'string' ? log.includes(needle) : needle.test(log) ); return filteredLogs.length > 0; diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 3b8321503d..c13e16bdf4 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -1 +1,5 @@ -export default ['packages/*/vitest.config.ts', 'examples/app/vitest.config.ts']; +export default [ + 'packages/*/vitest.config.ts', + 'examples/app/vitest.config.ts', + 'layers/vitest.config.ts', +];