npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@magnetikonline/edgy

v1.3.1

Published

Harness for authoring tests against AWS CloudFront Lambda@Edge functions.

Downloads

25

Readme

Edgy

Test

A harness to assist in the authoring of tests for Node.js based AWS CloudFront Lambda@Edge functions.

Installation

$ npm install @magnetikonline/edgy

What can this do?

Edgy provides the following:

  • Generation of Lambda@Edge event structures for the four available request life cycle points (viewer request, origin request, origin response, viewer response).
  • Execution of Lambda@Edge functions in a manner somewhat similar to the CloudFront runtime. Both async and older callback style handlers are supported.
  • Implements various checks and bounds (duck typing) of payloads returned from edge functions, with the execute(handler) harness function throwing errors for anything deemed to be malformed.
  • Captures the executed Lambda@Edge function payload, allowing for further testing and assertions.

Usage

Edgy provides four core constructors, which directly relate to each of the four life cycle points available in a CloudFront request. With an instance created, the desired event structure is then crafted and a supplied Lambda@Edge function executed against it.

ViewerRequest()

An example of crafting a viewer request event payload and executing a dummy function against it:

const edgy = require('@magnetikonline/edgy');

async function myTest() {
  const vReq = new edgy.ViewerRequest();
  vReq
    .setClientIp('1.2.3.4')
    .setHttpMethod('PUT')
    .setUri('/path/to/api/route')
    .addRequestHttpHeader('X-Fancy-Header','apples');

  const resp = await vReq.execute(
    // example Lambda@Edge function
    async function(event) {
      return event.Records[0].cf.request;
    }
  );

  console.dir(resp,{ depth: null });

  /*
  {
    clientIp: '1.2.3.4',
    headers: { 'x-fancy-header': [ { key: 'X-Fancy-Header', value: 'apples' } ] },
    method: 'PUT',
    querystring: '',
    uri: '/path/to/api/route'
  }
  */
}

Available methods:

OriginRequest()

An example of crafting a origin request event payload and executing a dummy function against it:

const edgy = require('@magnetikonline/edgy');

async function myTest() {
  const oReq = new edgy.OriginRequest();
  oReq
    .setClientIp('1.2.3.4')
    .setHttpMethod('POST')
    .setUri('/path/to/api/route')
    .addRequestHttpHeader('X-Fancy-Header','apples')
    .setOriginS3('mybucket.s3.ap-southeast-2.amazonaws.com','ap-southeast-2');

  const resp = await oReq.execute(
    // example Lambda@Edge function
    async function(event) {
      return event.Records[0].cf.request;
    }
  );

  console.dir(resp,{ depth: null });

  /*
  {
    clientIp: '1.2.3.4',
    headers: { 'x-fancy-header': [ { key: 'X-Fancy-Header', value: 'apples' } ] },
    method: 'POST',
    querystring: '',
    uri: '/path/to/api/route',
    origin: {
      s3: {
        authMethod: 'none',
        customHeaders: {},
        domainName: 'mybucket.s3.ap-southeast-2.amazonaws.com',
        path: '',
        region: 'ap-southeast-2'
      }
    }
  }
  */
}

Available methods:

OriginResponse()

An example of crafting a origin response event payload and executing a dummy function against it:

const edgy = require('@magnetikonline/edgy');

async function myTest() {
  const oRsp = new edgy.OriginResponse();
  oRsp
    .setResponseHttpStatusCode(202)
    .addResponseHttpHeader('X-Fancy-Header','oranges');

  const resp = await oRsp.execute(
    // example Lambda@Edge function
    async function(event) {
      return event.Records[0].cf.response;
    }
  );

  console.dir(resp,{ depth: null });

  /*
  {
    headers: { 'x-fancy-header': [ { key: 'X-Fancy-Header', value: 'oranges' } ] },
    status: '202',
    statusDescription: 'Accepted'
  }
  */
}

Available methods:

ViewerResponse()

An example of crafting a viewer response event payload and executing a dummy function against it:

const edgy = require('@magnetikonline/edgy');

async function myTest() {
  const vRsp = new edgy.ViewerResponse();
  vRsp
    .setResponseHttpStatusCode(304)
    .addResponseHttpHeader('X-Fancy-Header','oranges');

  const resp = await vRsp.execute(
    // example Lambda@Edge function
    async function(event) {
      return event.Records[0].cf.response;
    }
  );

  console.dir(resp,{ depth: null });

  /*
  {
    headers: { 'x-fancy-header': [ { key: 'X-Fancy-Header', value: 'oranges' } ] },
    status: '304',
    statusDescription: 'Not Modified'
  }
  */
}

Available methods:

Methods

setDistributionDomainName(name)

setDistributionId(id)

setRequestId(id)

Methods to set properties related to the CloudFront distribution:

const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();
harness
  .setDistributionDomainName('d111111abcdef8.cloudfront.net')
  .setDistributionId('EDFDVBD6EXAMPLE')
  .setRequestId('4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==');

/*
{
  Records: [
    {
      cf: {
        config: {
          distributionDomainName: 'd111111abcdef8.cloudfront.net',
          distributionId: 'EDFDVBD6EXAMPLE',
          requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ=='
        }
      }
    }
  ]
}
*/

setClientIp(ipAddr)

setHttpMethod(method)

setQuerystring(qs)

setUri(uri)

Methods to set general properties related to the request sent from the client:

const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();
harness
  .setClientIp('203.0.113.178')
  .setHttpMethod('GET')
  .setQuerystring('?key=value')
  .setUri('/path/to/route');

/*
{
  Records: [
    {
      cf: {
        request: {
          clientIp: '203.0.113.178',
          method: 'GET',
          querystring: 'key=value',
          uri: '/path/to/route'
        }
      }
    }
  ]
}
*/

setBody(data[,isTruncated])

Adds a collection of request body properties. The given data will be automatically base64 encoded:

const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();
harness.setBody('data payload',false);

/*
{
  Records: [
    {
      cf: {
        request: {
          body: {
            action: 'read-only',
            data: 'ZGF0YSBwYXlsb2Fk',
            encoding: 'base64',
            inputTruncated: false
          }
        }
      }
    }
  ]
}
*/

setRequestHttpHeader(key[,value])

addRequestHttpHeader(key,value)

Sets/adds HTTP headers to the request payload:

const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();
harness
  .addRequestHttpHeader('User-Agent','curl/8.4.0')
  .addRequestHttpHeader('X-Custom-Header','apples')
  .addRequestHttpHeader('X-Custom-Header','oranges');

/*
{
  Records: [
    {
      cf: {
        request: {
          headers: {
            'user-agent': [ { key: 'User-Agent', value: 'curl/8.4.0' } ],
            'x-custom-header': [
              { key: 'X-Custom-Header', value: 'apples' },
              { key: 'X-Custom-Header', value: 'oranges' }
            ]
          }
        }
      }
    }
  ]
}
*/

harness
  .setRequestHttpHeader('User-Agent','xyz')
  .setRequestHttpHeader('X-Custom-Header'); // remove HTTP header

/*
{
  Records: [
    {
      cf: {
        request: {
          headers: {
            'user-agent': [ { key: 'User-Agent', value: 'xyz' } ]
          }
        }
      }
    }
  ]
}
*/

setOriginCustom(domainName[,path])

setOriginKeepaliveTimeout(timeout)

setOriginPort(port)

setOriginHttps(isHttps)

setOriginReadTimeout(timeout)

setOriginSslProtocolList(protocolList)

Methods to define a custom origin property set for the request event payload:

const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();
harness
  .setOriginCustom('example.org','/custom/origin/path')
  .setOriginKeepaliveTimeout(35)
  .setOriginPort(1234)
  .setOriginHttps(true)
  .setOriginReadTimeout(25)
  .setOriginSslProtocolList(['TLSv1.1','TLSv1.2']);

/*
{
  Records: [
    {
      cf: {
        request: {
          origin: {
            custom: {
              customHeaders: {},
              domainName: 'example.org',
              keepaliveTimeout: 35,
              path: '/custom/origin/path',
              port: 1234,
              protocol: 'https',
              readTimeout: 25,
              sslProtocols: [ 'TLSv1.1', 'TLSv1.2' ]
            }
          }
        }
      }
    }
  ]
}
*/

setOriginS3(domainName[,region][,path])

setOriginOAI(isOAI)

Methods to define an S3 origin property set for the request event payload:

const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();
harness
  .setOriginS3(
    'mybucket.s3.ap-southeast-2.amazonaws.com',
    'ap-southeast-2',
    '/s3/bucket/path')
  .setOriginOAI(true);

/*
{
  Records: [
    {
      cf: {
        request: {
          origin: {
            s3: {
              authMethod: 'origin-access-identity',
              customHeaders: {},
              domainName: 'mybucket.s3.ap-southeast-2.amazonaws.com',
              path: '/s3/bucket/path',
              region: 'ap-southeast-2'
            }
          }
        }
      }
    }
  ]
}
*/

setOriginHttpHeader(key[,value])

addOriginHttpHeader(key,value)

Sets/adds HTTP headers to the origin request payload for custom and S3 targets:

const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();
harness
  .setOriginS3(
    'mybucket.s3.ap-southeast-2.amazonaws.com',
    'ap-southeast-2',
    '/s3/bucket/path')
  .addOriginHttpHeader('X-Custom-Header','apples')
  .addOriginHttpHeader('X-Custom-Header','oranges');

/*
{
  Records: [
    {
      cf: {
        request: {
          origin: {
            s3: {
              customHeaders: {
                'x-custom-header': [
                  { key: 'X-Custom-Header', value: 'apples' },
                  { key: 'X-Custom-Header', value: 'oranges' }
                ]
              }
            }
          }
        }
      }
    }
  ]
}
*/

harness.setOriginHttpHeader('X-Custom-Header'); // remove HTTP header

/*
{
  Records: [
    {
      cf: {
        request: {
          origin: {
            s3: {
              customHeaders: {}
            }
          }
        }
      }
    }
  ]
}
*/

setResponseHttpStatusCode(code)

setResponseHttpHeader(key[,value])

addResponseHttpHeader(key,value)

Methods to set properties related to the response received from the upstream CloudFront target:

const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();
harness
  .setResponseHttpStatusCode(304)
  .addResponseHttpHeader('X-Fancy-Header','oranges');

/*
{
  Records: [
    {
      cf: {
        response: {
          headers: {
            'x-fancy-header': [ { key: 'X-Fancy-Header', value: 'oranges' } ]
          },
          status: '304',
          statusDescription: 'Not Modified'
        }
      }
    }
  ]
}
*/

execute(handler)

Executes a Lambda@Edge function, passing a constructed event payload. Supports both async and older callback style function handlers.

After successful execution:

  • A series of validations are performed against the returned payload, verifying it should be a usable response for CloudFront to accept. In no way consider this to be comprehensive or complete - but should catch many obvious malformed payloads.
  • Return the transformed payload from the executed Lambda@Edge function, where additional assertions can then be performed.
const harness = new edgy.EVENT_TYPE_CONSTRUCTOR();

// -- construct event payload using instance methods --
// .setHttpMethod()
// .setUri()
// .setQuerystring()
// etc.

// execute function against payload
const resp = await harness.execute(
  // example Lambda@Edge function
  async function(event) {
    return event.Records[0].cf.response;
  }
);

Reference