Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to ruby 3.2 #28

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.ruby-gemset
.ruby-version
Gemfile.lock
serverless-output.yaml
7 changes: 5 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
source 'http://rubygems.org'

ruby '3.2.0'

gem 'sinatra'
gem 'json'
gem 'rack'
gem 'rackup'
gem 'rack-contrib'
gem 'aws-record'
gem 'rake'
gem 'rubysl-base64'

# These are the dependencies that are used only for unit tests.
group :test do
gem "rspec"
gem "rack-test"
gem 'rspec'
gem 'rack-test'
end
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ RSpec::Core::RakeTask.new :specs do |task|
task.pattern = Dir['spec/**/*_spec.rb']
end

task :default => ['specs']
task default: ['specs']
4 changes: 2 additions & 2 deletions app/config.ru
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
require 'rack'
require 'rack/contrib'
require_relative './server'
require_relative 'server'

set :root, File.dirname(__FILE__)
set :views, Proc.new { File.join(root, "views") }
set :views, proc { File.join(root, 'views') }

run Sinatra::Application
18 changes: 9 additions & 9 deletions app/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
require 'aws-record'

before do
if (! request.body.read.empty? and request.body.size > 0)
if request.body && request.body.read.empty? && request.body.size.positive?
request.body.rewind
@params = Sinatra::IndifferentHash.new
@params.merge!(JSON.parse(request.body.read))
Expand All @@ -21,12 +21,12 @@
##################################
get '/hello-world' do
content_type :json
{ :Output => 'Hello World!' }.to_json
{ Output: 'Hello World!' }.to_json
end

post '/hello-world' do
content_type :json
{ :Output => 'Hello World!' }.to_json
content_type :json
{ Output: 'Hello World!' }.to_json
end

##################################
Expand All @@ -49,11 +49,11 @@ class FeedbackServerlessSinatraTable

get '/api/feedback' do
content_type :json
items = FeedbackServerlessSinatraTable.scan()
items
.map { |r| { :ts => r.ts, :name => r.name, :feedback => r.feedback } }
.sort { |a, b| a[:ts] <=> b[:ts] }
.to_json
items = FeedbackServerlessSinatraTable.scan
items.
map { |r| { ts: r.ts, name: r.name, feedback: r.feedback } }.
sort { |a, b| a[:ts] <=> b[:ts] }.
to_json
end

post '/api/feedback' do
Expand Down
31 changes: 15 additions & 16 deletions lambda.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@
$app ||= Rack::Builder.parse_file("#{__dir__}/app/config.ru").first
ENV['RACK_ENV'] ||= 'production'


def handler(event:, context:)
# Check if the body is base64 encoded. If it is, try to decode it
body = if event['isBase64Encoded']
Base64.decode64 event['body']
else
event['body']
end || ''
Base64.decode64 event['body']
else
event['body']
end || ''

# Rack expects the querystring in plain text, not a hash
headers = event.fetch 'headers', {}
Expand All @@ -39,14 +38,14 @@ def handler(event:, context:)
'REQUEST_METHOD' => event.fetch('httpMethod'),
'SCRIPT_NAME' => '',
'PATH_INFO' => event.fetch('path', ''),
'QUERY_STRING' => (event['queryStringParameters'] || {}).map { |k,v| "#{k}=#{v}" }.join('&'),
'QUERY_STRING' => (event['queryStringParameters'] || {}).map { |k, v| "#{k}=#{v}" }.join('&'),
'SERVER_NAME' => headers.fetch('Host', 'localhost'),
'SERVER_PORT' => headers.fetch('X-Forwarded-Port', 443).to_s,

'rack.version' => Rack::VERSION,
'rack.url_scheme' => headers.fetch('CloudFront-Forwarded-Proto') { headers.fetch('X-Forwarded-Proto', 'https') },
'rack.input' => StringIO.new(body),
'rack.errors' => $stderr,
'rack.errors' => $stderr
}

# Pass request headers to Rack if they are available
Expand All @@ -55,11 +54,11 @@ def handler(event:, context:)
# Content-Type and Content-Length are handled specially per the Rack SPEC linked above.
name = key.upcase.gsub '-', '_'
header = case name
when 'CONTENT_TYPE', 'CONTENT_LENGTH'
name
else
"HTTP_#{name}"
end
when 'CONTENT_TYPE', 'CONTENT_LENGTH'
name
else
"HTTP_#{name}"
end
env[header] = value.to_s
end

Expand All @@ -68,7 +67,7 @@ def handler(event:, context:)
status, headers, body = $app.call env

# body is an array. We combine all the items to a single string
body_content = ""
body_content = ''
body.each do |item|
body_content += item.to_s
end
Expand All @@ -80,15 +79,15 @@ def handler(event:, context:)
'headers' => headers,
'body' => body_content
}
if event['requestContext'].has_key?('elb')
if event['requestContext'].key?('elb')
# Required if we use Application Load Balancer instead of API Gateway
response['isBase64Encoded'] = false
end
rescue Exception => exception
rescue Exception => e
# If there is _any_ exception, we return a 500 error with an error message
response = {
'statusCode' => 500,
'body' => exception.message
'body' => e.message
}
end

Expand Down
56 changes: 26 additions & 30 deletions spec/server_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,48 @@
include Rack::Test::Methods

# Test for HTTP GET for URL-matching pattern '/'
it "should return successfully on GET" do
it 'returns successfully on GET' do
get '/hello-world'
expect(last_response).to be_ok
json_result = JSON.parse(last_response.body)
expect(json_result["Output"]).to eq("Hello World!")
expect(json_result['Output']).to eq('Hello World!')
end

# Test for HTTP POST for URL-matching pattern '/'
it "should return successfully on POST" do
it 'returns successfully on POST' do
post '/hello-world'
expect(last_response).to be_ok
expect(json_result["Output"]).to eq("Hello World!")
expect(json_result['Output']).to eq('Hello World!')
end

it "should POST params to API feedback endpoint with success" do
expect(stub_client)
.to receive(:put_item)
.with({
:condition_expression=>be_kind_of(String),
:expression_attribute_names=>be_kind_of(Hash),
:item=>
{"feedback"=>"AWS Lambda + Ruby == <3",
"id"=>be_kind_of(String),
"name"=>"Tomas",
"ts"=>be_within(3).of(Time.now.to_i)},
:table_name=>"FeedbackServerlessSinatraTable"})
.and_call_original

api_gateway_post('/api/feedback', { name: "Tomas", feedback: "AWS Lambda + Ruby == <3" })
it 'POSTS params to API feedback endpoint with success' do
expect(stub_client).
to receive(:put_item).
with({
condition_expression: be_a(String),
expression_attribute_names: be_a(Hash),
item: { 'feedback' => 'AWS Lambda + Ruby == <3',
'id' => be_a(String),
'name' => 'Tomas',
'ts' => be_within(3).of(Time.now.to_i) },
table_name: 'FeedbackServerlessSinatraTable'
}).
and_call_original

api_gateway_post('/api/feedback', { name: 'Tomas', feedback: 'AWS Lambda + Ruby == <3' })

expect(last_response).to be_ok
end

it "should successfuly GET items from API feedback endpoint in right order" do
stub_client.stub_responses(:scan, :items => [
{'name' => 'Zdenka', "ts" => 2345678, "feedback" => "Halestorm"},
{'name' => 'Tomas', "ts" => 1234567, "feedback" => "Trivium"},
{'name' => 'xiangshen', "ts" => 5678901, "feedback" => "Awesome !"},
])
it 'successfulies GET items from API feedback endpoint in right order' do
stub_client.stub_responses(:scan, items: [
{ 'name' => 'Zdenka', 'ts' => 2_345_678, 'feedback' => 'Halestorm' },
{ 'name' => 'Tomas', 'ts' => 1_234_567, 'feedback' => 'Trivium' },
{ 'name' => 'xiangshen', 'ts' => 5_678_901, 'feedback' => 'Awesome !' }
])

get '/api/feedback'
expect(last_response).to be_ok
expect(json_result).to match_array([
{ "name" => "Tomas", "feedback"=>"Trivium", "ts"=> be_kind_of(String)},
{ "name" => "Zdenka", "feedback"=>"Halestorm", "ts"=> be_kind_of(String)},
{ "name" => "xiangshen","feedback"=>"Awesome !", "ts"=> be_kind_of(String)}
])
expect(json_result).to contain_exactly({ 'name' => 'Tomas', 'feedback' => 'Trivium', 'ts' => be_a(String) }, { 'name' => 'Zdenka', 'feedback' => 'Halestorm', 'ts' => be_a(String) }, { 'name' => 'xiangshen', 'feedback' => 'Awesome !', 'ts' => be_a(String) })
end
end
10 changes: 4 additions & 6 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
require_relative '../app/server.rb'
require_relative '../app/server'
require 'rack/test'

set :environment, :test

RSpec.configure do |config|
config.before(:each) do
config.before do
FeedbackServerlessSinatraTable.configure_client(client: stub_client)
end
end
Expand All @@ -14,9 +14,7 @@ def app
end

def stub_client
@stub_client ||= begin
Aws::DynamoDB::Client.new(stub_responses: true) # don't send real calls to DynamoDB in test env
end
@stub_client ||= Aws::DynamoDB::Client.new(stub_responses: true) # don't send real calls to DynamoDB in test env
end

# We could use native RSpec `post '/endpoint', param1: 'foo', param2: 'bar'
Expand All @@ -27,7 +25,7 @@ def api_gateway_post(path, params)
api_gateway_body_fwd = params.to_json
rack_input = StringIO.new(api_gateway_body_fwd)

post path, real_params = {}, {"rack.input" => rack_input}
post path, {}, { 'rack.input' => rack_input }
end

def json_result
Expand Down
2 changes: 1 addition & 1 deletion template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Resources:
Properties:
FunctionName: SinatraApp
Handler: lambda.handler
Runtime: ruby2.5
Runtime: ruby3.2
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref FeedbackTable
Expand Down