Repository: serverless/dashboard (language: python)

Issue:
SDK: Fix resolution of express param routes



Discussion / hints:

Fix patch:
diff --git a/node/packages/aws-lambda-sdk/package.json b/node/packages/aws-lambda-sdk/package.json
index 8e92b39e8..c84300c49 100644
--- a/node/packages/aws-lambda-sdk/package.json
+++ b/node/packages/aws-lambda-sdk/package.json
@@ -4,7 +4,7 @@
   "version": "0.14.19",
   "author": "Serverless, Inc.",
   "dependencies": {
-    "@serverless/sdk": "^0.5.13",
+    "@serverless/sdk": "^0.5.14",
     "@serverless/sdk-schema": "^0.15.3",
     "d": "^1.0.1",
     "ext": "^1.7.0",


Test patch:
diff --git a/node/packages/aws-lambda-sdk/test/fixtures/lambdas/express-nested.js b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/express-nested.js
deleted file mode 100644
index cf93bd233..000000000
--- a/node/packages/aws-lambda-sdk/test/fixtures/lambdas/express-nested.js
+++ /dev/null
@@ -1,18 +0,0 @@
-'use strict';
-
-const serverless = require('serverless-http');
-const express = require('express');
-
-const app = express();
-app.use(express.json());
-
-const router = new express.Router();
-router.post('/bar', (req, res) => {
-  res.send('"ok"');
-});
-
-app.use('/foo', router);
-
-app.use((req, res) => res.status(404).json({ error: 'Not Found' }));
-
-module.exports.handler = serverless(app);
diff --git a/node/packages/aws-lambda-sdk/test/fixtures/lambdas/express.js b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/express.js
index 5d1572fc8..9d5d18144 100644
--- a/node/packages/aws-lambda-sdk/test/fixtures/lambdas/express.js
+++ b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/express.js
@@ -18,6 +18,17 @@ app.post('/test', (req, res) => {
   res.send('"ok"');
 });
 
+const router = new express.Router();
+router.post('/bar', (req, res) => {
+  res.send('"ok"');
+});
+
+app.use('/nested', router);
+
+app.post('/users/:userId/books/:bookId', (req, res) => {
+  res.send('"ok"');
+});
+
 app.use((req, res) => res.status(404).json({ error: 'Not Found' }));
 
 module.exports.handler = serverless(app);
diff --git a/node/packages/aws-lambda-sdk/test/integration/index.test.js b/node/packages/aws-lambda-sdk/test/integration/index.test.js
index bb36fd694..80c40d698 100644
--- a/node/packages/aws-lambda-sdk/test/integration/index.test.js
+++ b/node/packages/aws-lambda-sdk/test/integration/index.test.js
@@ -71,7 +71,13 @@ describe('integration', function () {
 
     await awsRequest(ApiGatewayV2, 'createRoute', {
       ApiId: apiId,
-      RouteKey: 'POST /foo/bar',
+      RouteKey: 'POST /nested/bar',
+      Target: `integrations/${integrationId}`,
+    });
+
+    await awsRequest(ApiGatewayV2, 'createRoute', {
+      ApiId: apiId,
+      RouteKey: 'POST /users/{user}/books/{book}',
       Target: `integrations/${integrationId}`,
     });
 
@@ -1609,121 +1615,179 @@ describe('integration', function () {
     [
       'express',
       {
-        hooks: {
-          afterCreate: getCreateHttpApi('2.0'),
-          beforeDelete: async (testConfig) => {
-            await awsRequest(ApiGatewayV2, 'deleteApi', { ApiId: testConfig.apiId });
-          },
-        },
-        invoke: resolveExpressInvoke({ pathname: '/test' }),
-        test: ({ invocationsData, testConfig }) => {
-          for (const [
-            index,
+        variants: new Map([
+          [
+            'basic',
             {
-              trace: { spans },
-            },
-          ] of invocationsData.entries()) {
-            const lambdaSpan = spans.shift();
-            if (!index) spans.shift();
-            const { tags: lambdaTags } = lambdaSpan;
+              invoke: resolveExpressInvoke({ pathname: '/test' }),
+              test: ({ invocationsData, testConfig }) => {
+                for (const [
+                  index,
+                  {
+                    trace: { spans },
+                  },
+                ] of invocationsData.entries()) {
+                  const lambdaSpan = spans.shift();
+                  if (!index) spans.shift();
+                  const { tags: lambdaTags } = lambdaSpan;
 
-            expect(lambdaTags.aws.lambda.eventSource).to.equal('aws.apigateway');
-            expect(lambdaTags.aws.lambda.eventType).to.equal('aws.apigatewayv2.http.v2');
+                  expect(lambdaTags.aws.lambda.eventSource).to.equal('aws.apigateway');
+                  expect(lambdaTags.aws.lambda.eventType).to.equal('aws.apigatewayv2.http.v2');
 
-            expect(lambdaTags.aws.lambda.apiGateway).to.have.property('accountId');
-            expect(lambdaTags.aws.lambda.apiGateway.apiId).to.equal(testConfig.apiId);
-            expect(lambdaTags.aws.lambda.apiGateway.apiStage).to.equal('$default');
-            expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('id');
-            expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('timeEpoch');
-            expect(lambdaTags.aws.lambda.http).to.have.property('host');
-            expect(lambdaTags.aws.lambda.http).to.have.property('requestHeaderNames');
-            expect(lambdaTags.aws.lambda.http.method).to.equal('POST');
-            expect(lambdaTags.aws.lambda.http.path).to.equal('/test');
+                  expect(lambdaTags.aws.lambda.apiGateway).to.have.property('accountId');
+                  expect(lambdaTags.aws.lambda.apiGateway.apiId).to.equal(testConfig.apiId);
+                  expect(lambdaTags.aws.lambda.apiGateway.apiStage).to.equal('$default');
+                  expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('id');
+                  expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('timeEpoch');
+                  expect(lambdaTags.aws.lambda.http).to.have.property('host');
+                  expect(lambdaTags.aws.lambda.http).to.have.property('requestHeaderNames');
+                  expect(lambdaTags.aws.lambda.http.method).to.equal('POST');
+                  expect(lambdaTags.aws.lambda.http.path).to.equal('/test');
 
-            expect(lambdaTags.aws.lambda.http.statusCode.toString()).to.equal('200');
+                  expect(lambdaTags.aws.lambda.http.statusCode.toString()).to.equal('200');
 
-            expect(lambdaTags.aws.lambda.httpRouter.path.toString()).to.equal('/test');
+                  expect(lambdaTags.aws.lambda.httpRouter.path.toString()).to.equal('/test');
 
-            const [invocationSpan, expressSpan, ...middlewareSpans] = spans;
-            const routeSpan = middlewareSpans.pop();
-            const routerSpan = middlewareSpans[middlewareSpans.length - 1];
+                  const [invocationSpan, expressSpan, ...middlewareSpans] = spans;
+                  const routeSpan = middlewareSpans.pop();
+                  const routerSpan = middlewareSpans[middlewareSpans.length - 1];
 
-            expect(expressSpan.parentSpanId).to.deep.equal(invocationSpan.id);
+                  expect(expressSpan.parentSpanId).to.deep.equal(invocationSpan.id);
 
-            expect(middlewareSpans.map(({ name }) => name)).to.deep.equal([
-              'express.middleware.query',
-              'express.middleware.expressinit',
-              'express.middleware.jsonparser',
-              'express.middleware.router',
-            ]);
-            for (const middlewareSpan of middlewareSpans) {
-              expect(String(middlewareSpan.parentSpanId)).to.equal(String(expressSpan.id));
-            }
-            expect(routeSpan.name).to.equal('express.middleware.route.post.anonymous');
-            expect(String(routeSpan.parentSpanId)).to.equal(String(routerSpan.id));
-          }
-        },
-      },
-    ],
-    [
-      'express-nested',
-      {
-        hooks: {
-          afterCreate: getCreateHttpApi('2.0'),
-          beforeDelete: async (testConfig) => {
-            await awsRequest(ApiGatewayV2, 'deleteApi', { ApiId: testConfig.apiId });
-          },
-        },
-        invoke: resolveExpressInvoke({ pathname: '/foo/bar' }),
-        test: ({ invocationsData, testConfig }) => {
-          for (const [
-            index,
+                  expect(middlewareSpans.map(({ name }) => name)).to.deep.equal([
+                    'express.middleware.query',
+                    'express.middleware.expressinit',
+                    'express.middleware.jsonparser',
+                    'express.middleware.router',
+                  ]);
+                  for (const middlewareSpan of middlewareSpans) {
+                    expect(String(middlewareSpan.parentSpanId)).to.equal(String(expressSpan.id));
+                  }
+                  expect(routeSpan.name).to.equal('express.middleware.route.post.anonymous');
+                  expect(String(routeSpan.parentSpanId)).to.equal(String(routerSpan.id));
+                }
+              },
+            },
+          ],
+          [
+            'nested',
             {
-              trace: { spans },
+              invoke: resolveExpressInvoke({ pathname: '/nested/bar' }),
+              test: ({ invocationsData, testConfig }) => {
+                for (const [
+                  index,
+                  {
+                    trace: { spans },
+                  },
+                ] of invocationsData.entries()) {
+                  const lambdaSpan = spans.shift();
+                  if (!index) spans.shift();
+                  const { tags: lambdaTags } = lambdaSpan;
+
+                  expect(lambdaTags.aws.lambda.eventSource).to.equal('aws.apigateway');
+                  expect(lambdaTags.aws.lambda.eventType).to.equal('aws.apigatewayv2.http.v2');
+
+                  expect(lambdaTags.aws.lambda.apiGateway).to.have.property('accountId');
+                  expect(lambdaTags.aws.lambda.apiGateway.apiId).to.equal(testConfig.apiId);
+                  expect(lambdaTags.aws.lambda.apiGateway.apiStage).to.equal('$default');
+                  expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('id');
+                  expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('timeEpoch');
+                  expect(lambdaTags.aws.lambda.http).to.have.property('host');
+                  expect(lambdaTags.aws.lambda.http).to.have.property('requestHeaderNames');
+                  expect(lambdaTags.aws.lambda.http.method).to.equal('POST');
+                  expect(lambdaTags.aws.lambda.http.path).to.equal('/nested/bar');
+
+                  expect(lambdaTags.aws.lambda.http.statusCode.toString()).to.equal('200');
+
+                  expect(lambdaTags.aws.lambda.httpRouter.path.toString()).to.equal('/nested/bar');
+
+                  const [invocationSpan, expressSpan, ...middlewareSpans] = spans;
+                  const routeSpan = middlewareSpans.pop();
+                  const routerSpan = middlewareSpans.pop();
+                  const topRouterSpan = middlewareSpans[middlewareSpans.length - 1];
+
+                  expect(expressSpan.parentSpanId).to.deep.equal(invocationSpan.id);
+
+                  expect(middlewareSpans.map(({ name }) => name)).to.deep.equal([
+                    'express.middleware.query',
+                    'express.middleware.expressinit',
+                    'express.middleware.jsonparser',
+                    'express.middleware.router.nested',
+                  ]);
+                  for (const middlewareSpan of middlewareSpans) {
+                    expect(String(middlewareSpan.parentSpanId)).to.equal(String(expressSpan.id));
+                  }
+                  expect(routerSpan.name).to.equal('express.middleware.router');
+                  expect(String(routerSpan.parentSpanId)).to.equal(String(topRouterSpan.id));
+                  expect(routeSpan.name).to.equal('express.middleware.route.post.anonymous');
+                  expect(String(routeSpan.parentSpanId)).to.equal(String(routerSpan.id));
+                }
+              },
             },
-          ] of invocationsData.entries()) {
-            const lambdaSpan = spans.shift();
-            if (!index) spans.shift();
-            const { tags: lambdaTags } = lambdaSpan;
+          ],
+          [
+            'parametrized',
+            {
+              invoke: resolveExpressInvoke({ pathname: '/users/123/books/456' }),
+              test: ({ invocationsData, testConfig }) => {
+                for (const [
+                  index,
+                  {
+                    trace: { spans },
+                  },
+                ] of invocationsData.entries()) {
+                  const lambdaSpan = spans.shift();
+                  if (!index) spans.shift();
+                  const { tags: lambdaTags } = lambdaSpan;
 
-            expect(lambdaTags.aws.lambda.eventSource).to.equal('aws.apigateway');
-            expect(lambdaTags.aws.lambda.eventType).to.equal('aws.apigatewayv2.http.v2');
+                  expect(lambdaTags.aws.lambda.eventSource).to.equal('aws.apigateway');
+                  expect(lambdaTags.aws.lambda.eventType).to.equal('aws.apigatewayv2.http.v2');
 
-            expect(lambdaTags.aws.lambda.apiGateway).to.have.property('accountId');
-            expect(lambdaTags.aws.lambda.apiGateway.apiId).to.equal(testConfig.apiId);
-            expect(lambdaTags.aws.lambda.apiGateway.apiStage).to.equal('$default');
-            expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('id');
-            expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('timeEpoch');
-            expect(lambdaTags.aws.lambda.http).to.have.property('host');
-            expect(lambdaTags.aws.lambda.http).to.have.property('requestHeaderNames');
-            expect(lambdaTags.aws.lambda.http.method).to.equal('POST');
-            expect(lambdaTags.aws.lambda.http.path).to.equal('/foo/bar');
+                  expect(lambdaTags.aws.lambda.apiGateway).to.have.property('accountId');
+                  expect(lambdaTags.aws.lambda.apiGateway.apiId).to.equal(testConfig.apiId);
+                  expect(lambdaTags.aws.lambda.apiGateway.apiStage).to.equal('$default');
+                  expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('id');
+                  expect(lambdaTags.aws.lambda.apiGateway.request).to.have.property('timeEpoch');
+                  expect(lambdaTags.aws.lambda.http).to.have.property('host');
+                  expect(lambdaTags.aws.lambda.http).to.have.property('requestHeaderNames');
+                  expect(lambdaTags.aws.lambda.http.method).to.equal('POST');
+                  expect(lambdaTags.aws.lambda.http.path).to.equal('/users/123/books/456');
 
-            expect(lambdaTags.aws.lambda.http.statusCode.toString()).to.equal('200');
+                  expect(lambdaTags.aws.lambda.http.statusCode.toString()).to.equal('200');
 
-            expect(lambdaTags.aws.lambda.httpRouter.path.toString()).to.equal('/foo/bar');
+                  expect(lambdaTags.aws.lambda.httpRouter.path.toString()).to.equal(
+                    '/users/:userId/books/:bookId'
+                  );
 
-            const [invocationSpan, expressSpan, ...middlewareSpans] = spans;
-            const routeSpan = middlewareSpans.pop();
-            const routerSpan = middlewareSpans.pop();
-            const topRouterSpan = middlewareSpans[middlewareSpans.length - 1];
+                  const [invocationSpan, expressSpan, ...middlewareSpans] = spans;
+                  const routeSpan = middlewareSpans.pop();
+                  const routerSpan = middlewareSpans[middlewareSpans.length - 1];
 
-            expect(expressSpan.parentSpanId).to.deep.equal(invocationSpan.id);
+                  expect(expressSpan.parentSpanId).to.deep.equal(invocationSpan.id);
 
-            expect(middlewareSpans.map(({ name }) => name)).to.deep.equal([
-              'express.middleware.query',
-              'express.middleware.expressinit',
-              'express.middleware.jsonparser',
-              'express.middleware.router.foo',
-            ]);
-            for (const middlewareSpan of middlewareSpans) {
-              expect(String(middlewareSpan.parentSpanId)).to.equal(String(expressSpan.id));
-            }
-            expect(routerSpan.name).to.equal('express.middleware.router');
-            expect(String(routerSpan.parentSpanId)).to.equal(String(topRouterSpan.id));
-            expect(routeSpan.name).to.equal('express.middleware.route.post.anonymous');
-            expect(String(routeSpan.parentSpanId)).to.equal(String(routerSpan.id));
-          }
+                  expect(middlewareSpans.map(({ name }) => name)).to.deep.equal([
+                    'express.middleware.query',
+                    'express.middleware.expressinit',
+                    'express.middleware.jsonparser',
+                    'express.middleware.router',
+                  ]);
+                  for (const middlewareSpan of middlewareSpans) {
+                    expect(String(middlewareSpan.parentSpanId)).to.equal(String(expressSpan.id));
+                  }
+                  expect(routeSpan.name).to.equal('express.middleware.route.post.anonymous');
+                  expect(String(routeSpan.parentSpanId)).to.equal(String(routerSpan.id));
+                }
+              },
+            },
+          ],
+        ]),
+        config: {
+          hooks: {
+            afterCreate: getCreateHttpApi('2.0'),
+            beforeDelete: async (testConfig) => {
+              await awsRequest(ApiGatewayV2, 'deleteApi', { ApiId: testConfig.apiId });
+            },
+          },
         },
       },
     ],