Skip to content

Commit

Permalink
Setup tests with AWS' SAM CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
imjoehaines committed Apr 12, 2024
1 parent 8ae1301 commit 8741c6a
Show file tree
Hide file tree
Showing 12 changed files with 249 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.aws-sam
maze-runner.log
maze_output

Expand Down
23 changes: 23 additions & 0 deletions features/aws-lambda/handled.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Feature: Handled exceptions in AWS Lambda

# 3.9 is currently the minimum python version with a lambda runtime
@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8
Scenario: Handled exceptions are delivered in an AWS Lambda app
Given I run the lambda handler "handled" with the "event.json" event
When I wait to receive an error
Then the error is valid for the error reporting API version "4.0" for the "Python Bugsnag Notifier" notifier
And the event "unhandled" is false
And the event "severity" equals "warning"
And the event "severityReason.type" equals "handledException"
And the exception "errorClass" equals "Exception"
And the exception "message" equals "hello there"
And the exception "type" equals "python"
And the "file" of stack frame 0 equals "handled.py"
And the event "metaData.AWS Lambda Context.function_name" equals "BugsnagAwsLambdaTestFunction"
And the event "metaData.AWS Lambda Context.aws_request_id" is not null
And the event "metaData.AWS Lambda Event.path" equals "/hello"
And the event "metaData.AWS Lambda Event.httpMethod" equals "GET"
When I wait to receive a session
Then the session is valid for the session reporting API version "4.0" for the "Python Bugsnag Notifier" notifier
And the session payload has a valid sessions array
And the sessionCount "sessionsStarted" equals 1
10 changes: 10 additions & 0 deletions features/fixtures/aws-lambda/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ARG PYTHON_TEST_VERSION
FROM python:$PYTHON_TEST_VERSION

# install the SAM CLI
ENV SAM_CLI_TELEMETRY=0
RUN pip install --upgrade pip && pip install aws-sam-cli

COPY temp-bugsnag-python/ /usr/src/bugsnag

WORKDIR /usr/src/app
62 changes: 62 additions & 0 deletions features/fixtures/aws-lambda/app/events/event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"body": "{\"message\": \"hello world\"}",
"resource": "/hello",
"path": "/hello",
"httpMethod": "GET",
"isBase64Encoded": false,
"queryStringParameters": {
"foo": "bar"
},
"pathParameters": {
"proxy": "/path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8",
"Cache-Control": "max-age=0",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Custom User Agent String",
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "123456",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"requestTime": "09/Apr/2015:12:34:56 +0000",
"requestTimeEpoch": 1428582896000,
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"accessKey": null,
"sourceIp": "127.0.0.1",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "Custom User Agent String",
"user": null
},
"path": "/prod/hello",
"resourcePath": "/hello",
"httpMethod": "POST",
"apiId": "1234567890",
"protocol": "HTTP/1.1"
}
}
Empty file.
22 changes: 22 additions & 0 deletions features/fixtures/aws-lambda/app/src/handled.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os
import json
import bugsnag


bugsnag.configure(
api_key=os.environ["BUGSNAG_API_KEY"],
endpoint=os.environ["BUGSNAG_ERROR_ENDPOINT"],
session_endpoint=os.environ["BUGSNAG_SESSION_ENDPOINT"],
)


@bugsnag.aws_lambda_handler
def handler(event, context):
bugsnag.notify(Exception("hello there"))

return {
"statusCode": 200,
"body": json.dumps({
"message": "Did not crash!",
}),
}
1 change: 1 addition & 0 deletions features/fixtures/aws-lambda/app/src/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/usr/src/bugsnag
15 changes: 15 additions & 0 deletions features/fixtures/aws-lambda/app/src/unhandled.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os
import json
import bugsnag


bugsnag.configure(
api_key=os.environ["BUGSNAG_API_KEY"],
endpoint=os.environ["BUGSNAG_ERROR_ENDPOINT"],
session_endpoint=os.environ["BUGSNAG_SESSION_ENDPOINT"],
)


@bugsnag.aws_lambda_handler
def handler(event, context):
raise Exception("uh oh!")
37 changes: 37 additions & 0 deletions features/fixtures/aws-lambda/app/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: app

Globals:
Function:
Timeout: 30
Environment:
Variables:
BUGSNAG_API_KEY:
BUGSNAG_ERROR_ENDPOINT:
BUGSNAG_SESSION_ENDPOINT:

Parameters:
Runtime:
Type: String
AllowedPattern: python3\.\d+
ConstraintDescription: Must be a valid python runtime, e.g. "python3.12"
Handler:
Type: String
AllowedValues:
- handled
ConstraintDescription: Must be a file in the 'src' directory with a function named 'handler'

Resources:
BugsnagAwsLambdaTestFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: !Sub ${Handler}.handler
Runtime: !Ref Runtime
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
15 changes: 15 additions & 0 deletions features/fixtures/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,18 @@ services:
- BUGSNAG_SESSION_ENDPOINT
extra_hosts:
- "host.docker.internal:host-gateway"

aws-lambda:
build:
context: aws-lambda
args:
- PYTHON_TEST_VERSION
environment:
- BUGSNAG_API_KEY
- BUGSNAG_ERROR_ENDPOINT
- BUGSNAG_SESSION_ENDPOINT
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "./aws-lambda/app:/usr/src/app"
45 changes: 45 additions & 0 deletions features/steps/steps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require "os"

# PYTHON_TEST_VERSION is defined in env.rb
PARAMETER_OVERRIDES = "--parameter-overrides ParameterKey=Runtime,ParameterValue=python#{PYTHON_TEST_VERSION} ParameterKey=Handler,ParameterValue=handled"

def parameter_overrides(handler_name = nil)
overrides = [
"--parameter-overrides",
"ParameterKey=Runtime,ParameterValue=python#{PYTHON_TEST_VERSION}",
]

overrides << "ParameterKey=Handler,ParameterValue=#{handler_name}" unless handler_name.nil?

overrides.join(" ")
end

Given("I build the lambda function") do
step(%Q{I run the service "aws-lambda" with the command "sam build BugsnagAwsLambdaTestFunction #{parameter_overrides}"})
end

Given("I invoke the lambda handler {string}") do |handle_name|
command = [
"sam local invoke BugsnagAwsLambdaTestFunction",
"--container-host #{current_ip}",
"--container-host-interface 0.0.0.0",
"--docker-volume-basedir $PWD/features/fixtures/aws-lambda/app/.aws-sam/build",
parameter_overrides(handle_name),
]

step(%Q{I run the service "aws-lambda" with the command "#{command.join(" ")}"})
end

Given("I run the lambda function {string}") do |handler_name|
steps(%Q{
Given I build the lambda function
And I invoke the lambda handler "#{handler_name}"
})
end

Given("I run the lambda handler {string} with the {string} event") do |handler_name, event|
steps(%Q{
Given I build the lambda function
And I invoke the lambda handler "#{handler_name} --event events/#{event}"
})
end
22 changes: 18 additions & 4 deletions features/support/env.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
require "fileutils"

PYTHON_TEST_VERSION = ENV.fetch("PYTHON_TEST_VERSION")

def current_ip
return "host.docker.internal" if OS.mac?

ip_addr = `ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\\.){3}[0-9]*' | grep -v '127.0.0.1'`
ip_list = /((?:[0-9]*\.){3}[0-9]*)/.match(ip_addr)
ip_list.captures.first
end

Maze.hooks.before_all do
# log to console, not the filesystem
Maze.config.file_log = false
Expand Down Expand Up @@ -38,9 +48,13 @@
end

Maze.hooks.before do
host = "host.docker.internal"

Maze::Runner.environment["BUGSNAG_API_KEY"] = $api_key
Maze::Runner.environment["BUGSNAG_ERROR_ENDPOINT"] = "http://#{host}:#{Maze.config.port}/notify"
Maze::Runner.environment["BUGSNAG_SESSION_ENDPOINT"] = "http://#{host}:#{Maze.config.port}/sessions"
Maze::Runner.environment["BUGSNAG_ERROR_ENDPOINT"] = "http://#{current_ip}:#{Maze.config.port}/notify"
Maze::Runner.environment["BUGSNAG_SESSION_ENDPOINT"] = "http://#{current_ip}:#{Maze.config.port}/sessions"
end

5.upto(100) do |minor_version|
Before("@not-python-3.#{minor_version}") do
skip_this_scenario if PYTHON_TEST_VERSION == "3.#{minor_version}"
end
end

0 comments on commit 8741c6a

Please sign in to comment.