From e50667b3924f6ecab58a13ec15b7a41891a15e95 Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Tue, 10 Oct 2023 17:29:23 -0700 Subject: [PATCH 01/14] split homepage out into homepage and submit feature sets --- features/homepage.feature | 23 ----------------------- features/submit.feature | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 features/submit.feature diff --git a/features/homepage.feature b/features/homepage.feature index 4a96c4a..55bc3b4 100644 --- a/features/homepage.feature +++ b/features/homepage.feature @@ -10,26 +10,3 @@ Feature: Homepage And I should see the message "Enter a URL" And I should see a form field "url" And I should see a "Submit" button - - Scenario: Submitting the form without entering a URL - Given I visit the "/" page - When I click the "Submit" button - Then I should be on "/" page - And I should see the message "Please enter a valid URL" - - @db-test - Scenario: Submitting the form with a correct URL - Given I visit the "/" page - When I type "http://google.com" in the "url" field - And I click the "Submit" button - Then I should be on "/create" page - And I should see the message "Link created" - - @db-test - Scenario: Submitting the form with an existing URL - Given I visit the "/" page - And A link already exists with the url "http://google.com" - When I type "http://google.com" in the "url" field - And I click the "Submit" button - Then I should be on "/create" page - And I should see the message "Link exists" diff --git a/features/submit.feature b/features/submit.feature new file mode 100644 index 0000000..422e082 --- /dev/null +++ b/features/submit.feature @@ -0,0 +1,28 @@ +# features/submit.feature + +Feature: Submit + + Submitting URL's on the homepage + + Scenario: Submitting the form without entering a URL + Given I visit the "/" page + When I click the "Submit" button + Then I should be on "/" page + And I should see the message "Please enter a valid URL" + + @db-test + Scenario: Submitting the form with a correct URL + Given I visit the "/" page + When I type "http://google.com" in the "url" field + And I click the "Submit" button + Then I should be on "/create" page + And I should see the message "Link created" + + @db-test + Scenario: Submitting the form with an existing URL + Given I visit the "/" page + And A link already exists with the url "http://google.com" + When I type "http://google.com" in the "url" field + And I click the "Submit" button + Then I should be on "/create" page + And I should see the message "Link exists" From c45cf6ab512b3faee4bb38f27e10fc04571490d1 Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Wed, 11 Oct 2023 11:01:46 -0700 Subject: [PATCH 02/14] started building out rspec specs for API functionality --- spec/create_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 spec/create_spec.rb diff --git a/spec/create_spec.rb b/spec/create_spec.rb new file mode 100644 index 0000000..2e21d18 --- /dev/null +++ b/spec/create_spec.rb @@ -0,0 +1,21 @@ +require_relative '../.env' +ENV["DB_NAME"] = "test_#{ENV["DB_NAME"]}" +require_relative '../app' +require 'rubygems' +require 'roda' +require 'sequel' +require 'rspec' +require 'rack/test' + +def app + App +end + +describe "Submit API request to create new link" do + include Rack::Test::Methods + it "should return a link to the url provided" do + post '/create' + last_response.should be_ok + end +end + From 0dec27a01ceddd21cea1d5d2f7ac4a9c418bfa1b Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Wed, 11 Oct 2023 11:04:08 -0700 Subject: [PATCH 03/14] added rspec step in tests stage, adjusted should clause --- Jenkinsfile | 1 + spec/create_spec.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 6363195..a4d1c48 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,6 +26,7 @@ pipeline { stage('Run tests') { steps { sh 'cucumber features --format html --out coverage/cucumber.html' + sh 'rspec spec' } } stage('Report results') { diff --git a/spec/create_spec.rb b/spec/create_spec.rb index 2e21d18..558820e 100644 --- a/spec/create_spec.rb +++ b/spec/create_spec.rb @@ -13,7 +13,7 @@ end describe "Submit API request to create new link" do include Rack::Test::Methods - it "should return a link to the url provided" do + it "should return link data in json format" do post '/create' last_response.should be_ok end From 3421e18f21de0b231b9ac44b610cda18b913a6ce Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Wed, 11 Oct 2023 12:05:33 -0700 Subject: [PATCH 04/14] added more logic for running tests without killing the whole pipeline, and outputting rspec results --- Jenkinsfile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a4d1c48..f14f88f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,8 +25,12 @@ pipeline { } stage('Run tests') { steps { - sh 'cucumber features --format html --out coverage/cucumber.html' - sh 'rspec spec' + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + sh 'cucumber features --format html --out coverage/cucumber.html' + } + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + sh 'rspec spec --format html --out coverage/spec.html' + } } } stage('Report results') { @@ -37,9 +41,9 @@ pipeline { alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'coverage', - reportFiles: 'cucumber.html, index.html', + reportFiles: 'cucumber.html, spec.html, index.html', reportName: 'Test Results', - reportTitles: 'Cucumber Results, Test Coverage']) + reportTitles: 'Cucumber Results, RSpec Results, Test Coverage']) } } } From 7d6c098047226aaf10a23a390b412d99211b1020 Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Wed, 11 Oct 2023 19:18:47 -0700 Subject: [PATCH 05/14] added more to the spec test for creating a new link --- spec/create_spec.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/create_spec.rb b/spec/create_spec.rb index 558820e..19b6880 100644 --- a/spec/create_spec.rb +++ b/spec/create_spec.rb @@ -14,8 +14,13 @@ end describe "Submit API request to create new link" do include Rack::Test::Methods it "should return link data in json format" do - post '/create' - last_response.should be_ok + data = { + url: 'http://google.com' + } + res = post('/create', data.to_json, "CONTENT_TYPE" => "application/json") + expect(last_response).to be_ok + expect(JSON.parse(last_response.body)['url']).to eq(data[:url]) + expect(JSON.parse(last_response.body)['code']).not_to eq(nil) end end From f69c53bf295f060f1dc300463b49190db1eed052 Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Wed, 11 Oct 2023 19:20:07 -0700 Subject: [PATCH 06/14] removed unnecessary variable --- spec/create_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/create_spec.rb b/spec/create_spec.rb index 19b6880..b420272 100644 --- a/spec/create_spec.rb +++ b/spec/create_spec.rb @@ -17,7 +17,7 @@ describe "Submit API request to create new link" do data = { url: 'http://google.com' } - res = post('/create', data.to_json, "CONTENT_TYPE" => "application/json") + post('/create', data.to_json, "CONTENT_TYPE" => "application/json") expect(last_response).to be_ok expect(JSON.parse(last_response.body)['url']).to eq(data[:url]) expect(JSON.parse(last_response.body)['code']).not_to eq(nil) From 3ae20255f6971fa0cc32c73d0b7301c193391883 Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Wed, 11 Oct 2023 19:21:21 -0700 Subject: [PATCH 07/14] DRY it out a little --- spec/create_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/create_spec.rb b/spec/create_spec.rb index b420272..79deef8 100644 --- a/spec/create_spec.rb +++ b/spec/create_spec.rb @@ -19,8 +19,9 @@ describe "Submit API request to create new link" do } post('/create', data.to_json, "CONTENT_TYPE" => "application/json") expect(last_response).to be_ok - expect(JSON.parse(last_response.body)['url']).to eq(data[:url]) - expect(JSON.parse(last_response.body)['code']).not_to eq(nil) + response_json = JSON.parse(last_response.body) + expect(response_json['url']).to eq(data[:url]) + expect(response_json['code']).not_to eq(nil) end end From 4a821745264afaf9c2c6c83fb301ff3565e19ed9 Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Wed, 11 Oct 2023 19:21:43 -0700 Subject: [PATCH 08/14] implemented API create endpoint + added plugins to read json and access the header to parse the request for an API request + fixed logic where url param is nil + refactored the new code to a variable to be reused easier + if CONTENT_TYPE header is application/json, reply with json - there could be more refactoring (maybe separating it to a different endpoint) to handle other scenarios, we'll work on that later --- app.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app.rb b/app.rb index 2e49f5e..cd7c2b0 100644 --- a/app.rb +++ b/app.rb @@ -7,6 +7,8 @@ class App < Roda plugin :sessions, secret: ENV.delete('APP_SESSION_SECRET') plugin :render, escape: true plugin :flash + plugin :json_parser + plugin :request_headers DB = Sequel.sqlite("db/#{ENV['DB_NAME']}") links = DB[:links] @@ -27,7 +29,7 @@ class App < Roda r.post "create" do url = r.params['url'] - if url.empty? + if url.nil? or url.empty? flash['message'] = "Please enter a valid URL"; r.redirect '/' end @@ -36,8 +38,12 @@ class App < Roda links.insert(url: url, code: code) @message = "Link created" end + code = links.filter(:url => url).first[:code] @message ||= "Link exists" - @new_link = 'http://' + request.env['HTTP_HOST'] + '/' + links.filter(:url => url).first[:code] + @new_link = 'http://' + request.env['HTTP_HOST'] + '/' + code + if 'application/json' == r.headers['CONTENT_TYPE'] + return {url: url, code: code, link: @new_link}.to_json + end view :create end end From d1a0fc02222a7c4099269d77faddd0c3845eeebd Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Thu, 12 Oct 2023 11:32:00 -0700 Subject: [PATCH 09/14] change request to use the /links path, to fit more into a RESTful API format + also renamed the test to better describe the behavior --- spec/create_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/create_spec.rb b/spec/create_spec.rb index 79deef8..39cebfa 100644 --- a/spec/create_spec.rb +++ b/spec/create_spec.rb @@ -13,15 +13,16 @@ end describe "Submit API request to create new link" do include Rack::Test::Methods - it "should return link data in json format" do + it "should return link data in json format when a valid url is entered" do data = { url: 'http://google.com' } - post('/create', data.to_json, "CONTENT_TYPE" => "application/json") + post('/links', data.to_json, "CONTENT_TYPE" => "application/json") expect(last_response).to be_ok response_json = JSON.parse(last_response.body) expect(response_json['url']).to eq(data[:url]) expect(response_json['code']).not_to eq(nil) + expect(response_json['link']).to include(response_json['code']) end end From 2964e0663957218392fd94645f269826072a502a Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Thu, 12 Oct 2023 11:40:15 -0700 Subject: [PATCH 10/14] added before and after actions + before initializes the db + after cleans the db --- spec/create_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/create_spec.rb b/spec/create_spec.rb index 39cebfa..bcb4c25 100644 --- a/spec/create_spec.rb +++ b/spec/create_spec.rb @@ -13,6 +13,12 @@ end describe "Submit API request to create new link" do include Rack::Test::Methods + before :each do + @links = Sequel.sqlite("db/#{ENV['DB_NAME']}")[:links] + end + after :each do + @links.delete + end it "should return link data in json format when a valid url is entered" do data = { url: 'http://google.com' From 5ee9351362d78a55a7bbe7ad98b62b18981fbf47 Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Thu, 12 Oct 2023 11:42:14 -0700 Subject: [PATCH 11/14] added db initialization to make sure the db exists and is up to date before tests --- spec/create_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/create_spec.rb b/spec/create_spec.rb index bcb4c25..a088c88 100644 --- a/spec/create_spec.rb +++ b/spec/create_spec.rb @@ -7,6 +7,12 @@ require 'sequel' require 'rspec' require 'rack/test' +# DB initialization +Sequel.extension :migration +Sequel.sqlite("db/#{ENV['DB_NAME']}") do |db| + Sequel::Migrator.apply(db, "db/migrations") +end + def app App end From ac3e9f14cd5824f10750fd5a705beed3ef387663 Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Thu, 12 Oct 2023 11:42:43 -0700 Subject: [PATCH 12/14] added links route for API interactions --- app.rb | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/app.rb b/app.rb index cd7c2b0..5bd4ef7 100644 --- a/app.rb +++ b/app.rb @@ -41,10 +41,29 @@ class App < Roda code = links.filter(:url => url).first[:code] @message ||= "Link exists" @new_link = 'http://' + request.env['HTTP_HOST'] + '/' + code - if 'application/json' == r.headers['CONTENT_TYPE'] + view :create + end + r.on "links" do + r.post do + if 'application/json' != r.headers['CONTENT_TYPE'] + return {message: "Not a valid JSON request"}.to_json + end + + url = r.params['url'] + if url.nil? or url.empty? + response.status = 400 + return {message: "Please enter a valid URL"} + end + + if links.filter(:url => url).first.nil? + code = SecureRandom.urlsafe_base64 4 + links.insert(url: url, code: code) + end + + code = links.filter(:url => url).first[:code] + @new_link = 'http://' + request.env['HTTP_HOST'] + '/' + code return {url: url, code: code, link: @new_link}.to_json end - view :create end end end From 1e61a76ded54d266dac19a3de45e19b1f5b75e1d Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Thu, 12 Oct 2023 14:27:02 -0700 Subject: [PATCH 13/14] added 2 more tests for empty and missing url data posting to links --- spec/create_spec.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/create_spec.rb b/spec/create_spec.rb index a088c88..c814803 100644 --- a/spec/create_spec.rb +++ b/spec/create_spec.rb @@ -25,7 +25,7 @@ describe "Submit API request to create new link" do after :each do @links.delete end - it "should return link data in json format when a valid url is entered" do + it "should return link data in json format when a valid url is submitted" do data = { url: 'http://google.com' } @@ -36,5 +36,25 @@ describe "Submit API request to create new link" do expect(response_json['code']).not_to eq(nil) expect(response_json['link']).to include(response_json['code']) end + + it "should return with a 400 status and 'invalid url parameter' message when an empty url is submitted" do + data = { + url: '' + } + post('/links', data.to_json, "CONTENT_TYPE" => "application/json") + expect(last_response.status).to eq(400) + response_json = JSON.parse(last_response.body) + expect(response_json['message']).to eq('invalid url parameter') + + end + it "should return with a 400 status and 'missing url parameter' message when an empty url is submitted" do + data = { + } + post('/links', data.to_json, "CONTENT_TYPE" => "application/json") + expect(last_response.status).to eq(400) + response_json = JSON.parse(last_response.body) + expect(response_json['message']).to eq('missing url parameter') + + end end From cabb4daded8df2762c07c5d7fbef05a8572cf7be Mon Sep 17 00:00:00 2001 From: Adam Townsend Date: Thu, 12 Oct 2023 14:27:31 -0700 Subject: [PATCH 14/14] split up the nil? and empty? cases, because they are different situations --- app.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app.rb b/app.rb index 5bd4ef7..b8501a6 100644 --- a/app.rb +++ b/app.rb @@ -50,9 +50,14 @@ class App < Roda end url = r.params['url'] - if url.nil? or url.empty? + if url.nil? response.status = 400 - return {message: "Please enter a valid URL"} + return {message: "missing url parameter"}.to_json + end + + if url.empty? + response.status = 400 + return {message: "invalid url parameter"}.to_json end if links.filter(:url => url).first.nil?