From 7c58860688f3217a410ca39927d16593caff1609 Mon Sep 17 00:00:00 2001 From: BlazingRockStorm Date: Sun, 25 Aug 2024 00:48:25 +0900 Subject: [PATCH 1/5] edit test for Ruby 3 --- .gitignore | 1 + app/server.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7e86db3..337fab9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .ruby-gemset .ruby-version +Gemfile.lock \ No newline at end of file diff --git a/app/server.rb b/app/server.rb index 62b4ac8..559ebf9 100644 --- a/app/server.rb +++ b/app/server.rb @@ -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 > 0) request.body.rewind @params = Sinatra::IndifferentHash.new @params.merge!(JSON.parse(request.body.read)) From a719f7a64cb6865f33486c4aadc708cd552ffab9 Mon Sep 17 00:00:00 2001 From: BlazingRockStorm Date: Sun, 25 Aug 2024 09:56:48 +0900 Subject: [PATCH 2/5] update runtime --- template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template.yaml b/template.yaml index 02ad833..8fe2b34 100644 --- a/template.yaml +++ b/template.yaml @@ -7,7 +7,7 @@ Resources: Properties: FunctionName: SinatraApp Handler: lambda.handler - Runtime: ruby2.5 + Runtime: ruby3.2 Policies: - DynamoDBCrudPolicy: TableName: !Ref FeedbackTable From d0824f86de2a6f15a1e3de493b6e1a5b79c4c183 Mon Sep 17 00:00:00 2001 From: BlazingRockStorm Date: Sun, 25 Aug 2024 11:48:00 +0900 Subject: [PATCH 3/5] refactoring the code --- Gemfile | 6 +++-- Rakefile | 2 +- app/config.ru | 4 ++-- app/server.rb | 18 +++++++-------- lambda.rb | 31 ++++++++++++------------- spec/server_spec.rb | 56 +++++++++++++++++++++------------------------ spec/spec_helper.rb | 10 ++++---- 7 files changed, 61 insertions(+), 66 deletions(-) diff --git a/Gemfile b/Gemfile index 58ca4ea..72d5804 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,7 @@ source 'http://rubygems.org' +ruby '3.2.0' + gem 'sinatra' gem 'json' gem 'rack' @@ -10,6 +12,6 @@ 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 diff --git a/Rakefile b/Rakefile index 852a47e..0108a2c 100644 --- a/Rakefile +++ b/Rakefile @@ -4,4 +4,4 @@ RSpec::Core::RakeTask.new :specs do |task| task.pattern = Dir['spec/**/*_spec.rb'] end -task :default => ['specs'] +task default: ['specs'] diff --git a/app/config.ru b/app/config.ru index 79d85a5..40e7181 100644 --- a/app/config.ru +++ b/app/config.ru @@ -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 diff --git a/app/server.rb b/app/server.rb index 559ebf9..dd3fe9b 100644 --- a/app/server.rb +++ b/app/server.rb @@ -2,7 +2,7 @@ require 'aws-record' before do - if (request.body && request.body.read.empty? && 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)) @@ -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 ################################## @@ -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 diff --git a/lambda.rb b/lambda.rb index ee32628..3fa119b 100644 --- a/lambda.rb +++ b/lambda.rb @@ -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', {} @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/spec/server_spec.rb b/spec/server_spec.rb index a2acd25..2b7eee4 100644 --- a/spec/server_spec.rb +++ b/spec/server_spec.rb @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b01a2b1..83f00cc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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 @@ -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' @@ -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 From b1e207d14fa525b981690f2d2ad29d1d70287a32 Mon Sep 17 00:00:00 2001 From: BlazingRockStorm Date: Sun, 25 Aug 2024 11:51:48 +0900 Subject: [PATCH 4/5] add gem Rackup --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 72d5804..002ca21 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ ruby '3.2.0' gem 'sinatra' gem 'json' gem 'rack' +gem 'rackup' gem 'rack-contrib' gem 'aws-record' gem 'rake' From be2e8882ff201c5cec7bed2a1a2405000cc9f868 Mon Sep 17 00:00:00 2001 From: BlazingRockStorm Date: Sun, 25 Aug 2024 23:45:03 +0900 Subject: [PATCH 5/5] add serverless-output.yaml to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 337fab9..b71abd0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .ruby-gemset .ruby-version -Gemfile.lock \ No newline at end of file +Gemfile.lock +serverless-output.yaml \ No newline at end of file