From ee7d15e4c41c01e2a07a011070f77c854cfa2f78 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Mon, 11 Sep 2023 09:37:46 +0200 Subject: [PATCH] feat: update to BDD Markdown v6 (#243) See https://github.com/NordicSemiconductor/bdd-markdown-js/pull/62 --- features/Webhook.feature.md | 5 ++ features/markdown-reporter.ts | 4 +- features/steps/rest-steps.ts | 80 +++++++++++++++--------------- features/steps/webhook-receiver.ts | 12 +++-- features/steps/webhook-steps.ts | 63 ++++++++++------------- features/traceToMermaid.ts | 25 ++++++---- package-lock.json | 27 +++++----- package.json | 3 +- 8 files changed, 113 insertions(+), 106 deletions(-) diff --git a/features/Webhook.feature.md b/features/Webhook.feature.md index 6a266157..c3005d3d 100644 --- a/features/Webhook.feature.md +++ b/features/Webhook.feature.md @@ -1,3 +1,8 @@ +--- +exampleContext: + webhookReceiver: https://example.com +--- + # Demonstrate the Webhook Receiver > As the author of a software component diff --git a/features/markdown-reporter.ts b/features/markdown-reporter.ts index 31ceca33..c06aaed4 100644 --- a/features/markdown-reporter.ts +++ b/features/markdown-reporter.ts @@ -1,5 +1,5 @@ import { markdownReporter } from '@nordicsemiconductor/bdd-markdown' -process.stdin.on('data', (data) => { - console.log(markdownReporter(JSON.parse(data.toString()))) +process.stdin.on('data', async (data) => { + console.log(await markdownReporter(JSON.parse(data.toString()))) }) diff --git a/features/steps/rest-steps.ts b/features/steps/rest-steps.ts index 08a79cd3..2061b4be 100644 --- a/features/steps/rest-steps.ts +++ b/features/steps/rest-steps.ts @@ -1,56 +1,58 @@ import { codeBlockOrThrow, - noMatch, StepRunner, - StepRunnerArgs, - StepRunResult, + groupMatcher, } from '@nordicsemiconductor/bdd-markdown' import assert from 'assert/strict' import fetch, { Response } from 'node-fetch' import { World } from '../run-features.js' +import { Type } from '@sinclair/typebox' export const steps = (): StepRunner[] => { let baseUrl: URL | undefined = undefined let res: Response | undefined = undefined return [ - async ({ step }: StepRunnerArgs): Promise => { - const match = /^the endpoint is `(?[^`]+)`$/.exec(step.title) - if (match === null) return noMatch - baseUrl = new URL(match.groups?.endpoint ?? '') - }, - async ({ - step, - log: { - step: { progress }, - feature: { progress: featureProgress }, + groupMatcher( + { + regExp: /^the endpoint is `(?[^`]+)`$/, + schema: Type.Object({ endpoint: Type.String() }), }, - }: StepRunnerArgs): Promise => { - const match = - /^I (?POST) to `(?[^`]+)` with this JSON$/.exec( - step.title, - ) - if (match === null) return noMatch - const url = new URL(match.groups?.resource ?? '/', baseUrl).toString() - const method = match.groups?.method ?? 'GET' - progress(`${method} ${url}`) - const body = codeBlockOrThrow(step).code - progress(body) - - res = await fetch(url, { - method, - body, - }) + async ({ match: { endpoint } }) => { + baseUrl = new URL(endpoint) + }, + ), + groupMatcher( + { + regExp: /^I (?POST) to `(?[^`]+)` with this JSON$/, + schema: Type.Object({ method: Type.String(), resource: Type.String() }), + }, + async ({ match: { method, resource }, log: { progress }, step }) => { + const url = new URL(resource, baseUrl).toString() + progress(`${method} ${url}`) + const body = codeBlockOrThrow(step).code + progress(body) - progress(`${res.status} ${res.statusText}`) - featureProgress(`x-amzn-trace-id: ${res.headers.get('x-amzn-trace-id')}`) - }, - async ({ step }: StepRunnerArgs): Promise => { - const match = /^the response status code should be (?[0-9]+)$/.exec( - step.title, - ) - if (match === null) return noMatch + res = await fetch(url, { + method, + body, + }) - assert.equal(res?.status, parseInt(match.groups?.code ?? '-1', 10)) - }, + progress(`${res.status} ${res.statusText}`) + progress(`x-amzn-trace-id: ${res.headers.get('x-amzn-trace-id')}`) + }, + ), + groupMatcher( + { + regExp: /^the response status code should be (?[0-9]+)$/, + schema: Type.Object({ code: Type.Integer() }), +converters: { + code: (s) => parseInt(s, 10), + }, + + }, + async ({ match: { code } }) => { + assert.equal(res?.status, code) + }, + ), ] } diff --git a/features/steps/webhook-receiver.ts b/features/steps/webhook-receiver.ts index 3ea31964..98cfcb4a 100644 --- a/features/steps/webhook-receiver.ts +++ b/features/steps/webhook-receiver.ts @@ -3,7 +3,7 @@ import { ReceiveMessageCommand, SQSClient, } from '@aws-sdk/client-sqs' -import { Logger, Scenario } from '@nordicsemiconductor/bdd-markdown' +import { Logger } from '@nordicsemiconductor/bdd-markdown' type WebhookRequest = { headers: { [key: string]: string } @@ -34,7 +34,7 @@ export class WebhookReceiver { */ async receiveWebhookRequest( MessageGroupId: string, - log: Logger, + log: Logger, ): Promise { const { Messages } = await this.sqs.send( new ReceiveMessageCommand({ @@ -46,10 +46,12 @@ export class WebhookReceiver { }), ) - if (Messages === undefined || !Messages.length) { + const msg = Messages?.[0] + + if (msg === undefined) { throw new Error('No webhook request received!') } - const { Body, MessageAttributes, ReceiptHandle, Attributes } = Messages[0] + const { Body, MessageAttributes, ReceiptHandle, Attributes } = msg await this.sqs.send( new DeleteMessageCommand({ QueueUrl: this.queueUrl, @@ -59,7 +61,7 @@ export class WebhookReceiver { if (Attributes === undefined || MessageAttributes === undefined) throw new Error( - `No attributes defined in Message "${JSON.stringify(Messages[0])}"!`, + `No attributes defined in Message "${JSON.stringify(msg)}"!`, ) const attrs = MessageAttributes diff --git a/features/steps/webhook-steps.ts b/features/steps/webhook-steps.ts index 3199ecbd..6c0a221b 100644 --- a/features/steps/webhook-steps.ts +++ b/features/steps/webhook-steps.ts @@ -1,53 +1,44 @@ import { codeBlockOrThrow, - noMatch, + groupMatcher, StepRunner, - StepRunnerArgs, - StepRunResult, } from '@nordicsemiconductor/bdd-markdown' import assert from 'assert/strict' import { World } from '../run-features.js' import { WebhookReceiver } from './webhook-receiver.js' +import { Type } from '@sinclair/typebox' export const steps = (): StepRunner[] => { let r: WebhookReceiver return [ - async ({ - step, - log: { step: log }, - context: { webhookQueue }, - }: StepRunnerArgs): Promise => { - if (!/^I have a Webhook Receiver$/.test(step.title)) return noMatch - log.progress(`Webhook queue: ${webhookQueue}`) - r = new WebhookReceiver({ queueUrl: webhookQueue }) - await r.clearQueue() + { + match: (title) => /^I have a Webhook Receiver$/.test(title), + run: async ({ log, context: { webhookQueue } }) => { + log.progress(`Webhook queue: ${webhookQueue}`) + r = new WebhookReceiver({ queueUrl: webhookQueue }) + await r.clearQueue() + }, }, - async ({ - step, - log: { scenario: log }, - }: StepRunnerArgs): Promise => { - const match = - /^the Webhook Receiver `(?[^"]+)` should be called$/.exec( - step.title, + groupMatcher( + { + regExp: + /^the Webhook Receiver `(?[^"]+)` should be called$/, + schema: Type.Object({ MessageGroupId: Type.String() }), + }, + async ({ match: { MessageGroupId }, log }) => { + await r.receiveWebhookRequest(MessageGroupId, log) + }, + ), + { + match: (title) => + /^the webhook request body should equal this JSON$/.test(title), + run: async ({ step }) => { + assert.deepEqual( + JSON.parse(codeBlockOrThrow(step).code), + r.latestWebhookRequest?.body, ) - if (match === null) return noMatch - - const request = await r.receiveWebhookRequest( - match.groups?.MessageGroupId as string, - log, - ) - - return { result: request.body } - }, - async ({ step }: StepRunnerArgs): Promise => { - if (!/^the webhook request body should equal this JSON$/.test(step.title)) - return noMatch - - assert.deepEqual( - JSON.parse(codeBlockOrThrow(step).code), - r.latestWebhookRequest?.body, - ) + }, }, ] } diff --git a/features/traceToMermaid.ts b/features/traceToMermaid.ts index 97cf109c..843a4121 100644 --- a/features/traceToMermaid.ts +++ b/features/traceToMermaid.ts @@ -29,16 +29,21 @@ const suiteResult = await new Promise((resolve) => }), ) -for await (const [f, result] of suiteResult.results) { - const traceIds = result.logs - .filter( - ({ level, message }) => - level === LogLevel.PROGRESS && - message.find((m) => m.startsWith('x-amzn-trace-id')), - ) - .map(({ message }) => message) - .flat() - .map((s) => s.split(' ')[1].split(';')[0].split('=')[1]) +for (const [f, featureResult] of suiteResult.results) { + const traceIds = featureResult.results.flatMap(([, scenarioResult]) => + scenarioResult.results.flatMap(([, { logs }]) => + logs + .filter( + ({ level, message }) => + level === LogLevel.PROGRESS && + message.find((m) => m.startsWith('x-amzn-trace-id')), + ) + .map(({ message }) => message) + .flat() + .map((s) => s.split(' ')[1].split(';')[0].split('=')[1]), + ), + ) + if (traceIds.length === 0) continue const trace = await xray.send( diff --git a/package-lock.json b/package-lock.json index 1ea9e255..bea13541 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,9 @@ "@aws-sdk/client-sqs": "3.409.0", "@aws-sdk/client-xray": "3.409.0", "@nordicsemiconductor/asset-tracker-cloud-code-style": "12.0.85", - "@nordicsemiconductor/bdd-markdown": "5.6.19", + "@nordicsemiconductor/bdd-markdown": "6.0.0", "@nordicsemiconductor/cloudformation-helpers": "8.0.0", + "@sinclair/typebox": "0.31.14", "@swc/core": "1.3.83", "@swc/jest": "0.2.29", "@types/aws-lambda": "8.10.119", @@ -2730,6 +2731,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/schemas/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@jest/source-map": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", @@ -2943,9 +2950,9 @@ } }, "node_modules/@nordicsemiconductor/bdd-markdown": { - "version": "5.6.19", - "resolved": "https://registry.npmjs.org/@nordicsemiconductor/bdd-markdown/-/bdd-markdown-5.6.19.tgz", - "integrity": "sha512-0P6m3yEo7WZXpjfYdkca+EarJ34GuplPNllOklfH/cU87rfUMmXab/Kybw4N3wE54hNOIXwpsbZMRWFMJnGbTw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nordicsemiconductor/bdd-markdown/-/bdd-markdown-6.0.0.tgz", + "integrity": "sha512-u5lhERdasZIWiUdxCcFUZF5X9jqttcldOxX+BPK66JU+/YrbVtJJMlU7JtLgxAwrGYSLSCGmRwhWHwFaMV9UjQ==", "dev": true, "dependencies": { "@sinclair/typebox": "0.31.14", @@ -2960,12 +2967,6 @@ "npm": ">=9.0.0" } }, - "node_modules/@nordicsemiconductor/bdd-markdown/node_modules/@sinclair/typebox": { - "version": "0.31.14", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.14.tgz", - "integrity": "sha512-2spk0ie6J/4r+nwb55OtBXUn5cZLF9S98fopIjuutBVoq8yLRNh+h8QvMkCjMu5gWBMnnZ/PUSXeHE3xGBPKLQ==", - "dev": true - }, "node_modules/@nordicsemiconductor/bdd-markdown/node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -3050,9 +3051,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.31.14", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.14.tgz", + "integrity": "sha512-2spk0ie6J/4r+nwb55OtBXUn5cZLF9S98fopIjuutBVoq8yLRNh+h8QvMkCjMu5gWBMnnZ/PUSXeHE3xGBPKLQ==", "dev": true }, "node_modules/@sinonjs/commons": { diff --git a/package.json b/package.json index bdc6e684..a90f6aa8 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,9 @@ "@aws-sdk/client-sqs": "3.409.0", "@aws-sdk/client-xray": "3.409.0", "@nordicsemiconductor/asset-tracker-cloud-code-style": "12.0.85", - "@nordicsemiconductor/bdd-markdown": "5.6.19", + "@nordicsemiconductor/bdd-markdown": "6.0.0", "@nordicsemiconductor/cloudformation-helpers": "8.0.0", + "@sinclair/typebox": "0.31.14", "@swc/core": "1.3.83", "@swc/jest": "0.2.29", "@types/aws-lambda": "8.10.119",