Compare commits

..

No commits in common. "master" and "2023-10-15" have entirely different histories.

14 changed files with 107 additions and 334 deletions

2
.gitignore vendored
View File

@ -3,5 +3,3 @@
*.db *.db
.env.rb .env.rb
coverage coverage
vendor
.ruby-version

View File

@ -1,6 +0,0 @@
AllCops:
NewCops: enable
SuggestExtensions: false
Metrics/BlockLength:
AllowedMethods: ['describe', 'context', 'route', 'r.on', 'r.post']

33
Gemfile
View File

@ -1,35 +1,32 @@
# frozen_string_literal: true # frozen_string_literal: true
source 'https://rubygems.org' source "https://rubygems.org"
gem 'rackup', '~> 2.1'
gem 'falcon', '~> 0.42.3' gem "roda", "~> 3.72"
gem 'roda', '~> 3.72' gem "sequel", "~> 5.72"
gem 'sequel', '~> 5.72' gem "tilt", "~> 2.2"
gem 'tilt', '~> 2.2' gem "sqlite3", "~> 1.6"
gem 'sqlite3', '~> 1.6' gem "erubi", "~> 1.12"
gem 'erubi', '~> 1.12'
group :test do group :test do
gem 'cucumber', '~> 9.0'
gem 'capybara', '~> 3.39' gem "cucumber", "~> 9.0"
gem 'rspec', '~> 3.12' gem "capybara", "~> 3.39"
gem 'selenium-webdriver', '~> 4.13' gem "rspec", "~> 3.12"
gem 'simplecov' gem "selenium-webdriver", "~> 4.13"
gem 'rubocop', require: false gem "simplecov"
gem 'ruby_audit', '~> 2.2'
gem 'bundle-audit', '~> 0.1.0'
end end
gem "puma", "~> 6.4"
gem "rackup", "~> 2.1"

View File

@ -3,38 +3,8 @@ GEM
specs: specs:
addressable (2.8.5) addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
async (2.6.5)
console (~> 1.10)
fiber-annotation
io-event (~> 1.1)
timers (~> 4.1)
async-container (0.16.12)
async
async-io
async-http (0.61.0)
async (>= 1.25)
async-io (>= 1.28)
async-pool (>= 0.2)
protocol-http (~> 0.25.0)
protocol-http1 (~> 0.16.0)
protocol-http2 (~> 0.15.0)
traces (>= 0.10.0)
async-http-cache (0.4.3)
async-http (~> 0.56)
async-io (1.37.0)
async
async-pool (0.4.0)
async (>= 1.25)
base64 (0.1.1)
bigdecimal (3.1.4) bigdecimal (3.1.4)
build-environment (1.13.0)
builder (3.2.4) builder (3.2.4)
bundle-audit (0.1.0)
bundler-audit
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
capybara (3.39.2) capybara (3.39.2)
addressable addressable
matrix matrix
@ -44,9 +14,6 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
console (1.23.2)
fiber-annotation
fiber-local
cucumber (9.0.2) cucumber (9.0.2)
builder (~> 3.2, >= 3.2.4) builder (~> 3.2, >= 3.2.4)
cucumber-ci-environment (~> 9.2, >= 9.2.0) cucumber-ci-environment (~> 9.2, >= 9.2.0)
@ -74,53 +41,18 @@ GEM
diff-lcs (1.5.0) diff-lcs (1.5.0)
docile (1.4.0) docile (1.4.0)
erubi (1.12.0) erubi (1.12.0)
falcon (0.42.3)
async
async-container (~> 0.16.0)
async-http (~> 0.57)
async-http-cache (~> 0.4.0)
async-io (~> 1.22)
build-environment (~> 1.13)
bundler
localhost (~> 1.1)
openssl (~> 3.0)
process-metrics (~> 0.2.0)
protocol-rack (~> 0.1)
samovar (~> 2.1)
ffi (1.16.2) ffi (1.16.2)
fiber-annotation (0.2.0)
fiber-local (1.0.0)
io-event (1.3.3)
json (2.6.3)
language_server-protocol (3.17.0.3)
localhost (1.1.10)
mapping (1.1.1)
matrix (0.4.2) matrix (0.4.2)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.4) mini_portile2 (2.8.4)
multi_test (1.1.0) multi_test (1.1.0)
nio4r (2.5.9)
nokogiri (1.15.4) nokogiri (1.15.4)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
openssl (3.2.0)
parallel (1.23.0)
parser (3.2.2.4)
ast (~> 2.4.1)
racc
process-metrics (0.2.1)
console (~> 1.8)
samovar (~> 2.1)
protocol-hpack (1.4.2)
protocol-http (0.25.0)
protocol-http1 (0.16.0)
protocol-http (~> 0.22)
protocol-http2 (0.15.1)
protocol-hpack (~> 1.4)
protocol-http (~> 0.18)
protocol-rack (0.2.6)
protocol-http (~> 0.23)
rack (>= 1.0)
public_suffix (5.0.3) public_suffix (5.0.3)
puma (6.4.0)
nio4r (~> 2.0)
racc (1.7.1) racc (1.7.1)
rack (3.0.8) rack (3.0.8)
rack-test (2.1.0) rack-test (2.1.0)
@ -128,7 +60,6 @@ GEM
rackup (2.1.0) rackup (2.1.0)
rack (>= 3) rack (>= 3)
webrick (~> 1.8) webrick (~> 1.8)
rainbow (3.1.1)
regexp_parser (2.8.1) regexp_parser (2.8.1)
rexml (3.2.6) rexml (3.2.6)
roda (3.72.0) roda (3.72.0)
@ -146,27 +77,7 @@ GEM
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-support (3.12.1) rspec-support (3.12.1)
rubocop (1.57.1)
base64 (~> 0.1.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.2.2.4)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
ruby_audit (2.2.0)
bundler-audit (~> 0.9.0)
rubyzip (2.3.2) rubyzip (2.3.2)
samovar (2.2.0)
console (~> 1.0)
mapping (~> 1.0)
selenium-webdriver (4.13.1) selenium-webdriver (4.13.1)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
@ -183,11 +94,7 @@ GEM
mini_portile2 (~> 2.8.0) mini_portile2 (~> 2.8.0)
sys-uname (1.2.3) sys-uname (1.2.3)
ffi (~> 1.1) ffi (~> 1.1)
thor (1.3.0)
tilt (2.3.0) tilt (2.3.0)
timers (4.3.5)
traces (0.11.1)
unicode-display_width (2.5.0)
webrick (1.8.1) webrick (1.8.1)
websocket (1.2.10) websocket (1.2.10)
xpath (3.2.0) xpath (3.2.0)
@ -197,16 +104,13 @@ PLATFORMS
x86_64-freebsd-13 x86_64-freebsd-13
DEPENDENCIES DEPENDENCIES
bundle-audit (~> 0.1.0)
capybara (~> 3.39) capybara (~> 3.39)
cucumber (~> 9.0) cucumber (~> 9.0)
erubi (~> 1.12) erubi (~> 1.12)
falcon (~> 0.42.3) puma (~> 6.4)
rackup (~> 2.1) rackup (~> 2.1)
roda (~> 3.72) roda (~> 3.72)
rspec (~> 3.12) rspec (~> 3.12)
rubocop
ruby_audit (~> 2.2)
selenium-webdriver (~> 4.13) selenium-webdriver (~> 4.13)
sequel (~> 5.72) sequel (~> 5.72)
simplecov simplecov

136
Jenkinsfile vendored
View File

@ -1,21 +1,20 @@
def buildArtifact = true
pipeline { pipeline {
agent { label 'ruby && freebsd' } agent any
environment { environment {
APP_SESSION_SECRET = ''
DB_NAME = 'url_shortener.db' DB_NAME = 'url_shortener.db'
} }
stages { stages {
stage('Init') { stage('Init') {
steps { steps {
sh ''' #!/usr/local/bin/bash sh 'rbenv local 3.2.2'
rbenv local 3.2.2 script {
echo "# frozen_string_literal: true\n" > .env.rb env.APP_SESSION_SECRET = sh(script: 'ruby secret.rb', returnStdout: true)
echo "ENV['APP_SESSION_SECRET'] ||= '$(ruby -rsecurerandom -e 'puts SecureRandom.base64(64)')'" >> .env.rb }
echo "ENV['DB_NAME'] ||= '${DB_NAME}'" >> .env.rb sh 'echo "ENV[\\\"APP_SESSION_SECRET\\\"] ||= $(ruby secret.rb)" > .env.rb'
cat .env.rb sh 'echo "ENV[\\\"DB_NAME\\\"] ||= \\\"${DB_NAME}\\\"" >> .env.rb'
''' sh 'cat .env.rb'
} }
} }
stage('Build dependencies') { stage('Build dependencies') {
@ -24,131 +23,27 @@ pipeline {
sh 'sequel -m db/migrations sqlite://db/${DB_NAME}' sh 'sequel -m db/migrations sqlite://db/${DB_NAME}'
} }
} }
stage('Audit Dependencies') {
steps {
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
script {
try {
sh 'bundle exec ruby-audit check'
sh 'bundle exec bundle-audit check >> audit.html'
} catch (e) {
script {
buildArtifact = false
}
}
}
}
}
}
stage('Code Linting') {
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
script {
try {
sh 'bundle exec rubocop --format html --out rubocop.html'
} catch (e) {
script {
buildArtifact = false
}
}
}
}
}
}
stage('Run tests') { stage('Run tests') {
steps { steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
script { sh 'cucumber features --format html --out cucumber.html'
try {
sh 'bundle exec cucumber features --format html --out cucumber.html'
} catch (e) {
script {
buildArtifact = false
}
}
}
} }
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
script { sh 'rspec spec --format html --out spec.html'
try {
sh 'bundle exec rspec spec --format html --out spec.html'
} catch (e) {
script {
buildArtifact = false
}
}
}
} }
} }
} }
stage('Report results') { stage('Report results') {
steps { steps {
archive(includes: 'pkg/*.gem')
publishHTML (target: [ publishHTML (target: [
allowMissing: false, allowMissing: false,
alwaysLinkToLastBuild: false, alwaysLinkToLastBuild: false,
keepAll: true, keepAll: true,
reportDir: '.', reportDir: '.',
reportFiles: 'rubocop.html, audit.html, cucumber.html, spec.html, coverage/index.html', reportFiles: 'cucumber.html, spec.html, coverage/index.html',
reportName: 'Test Results', reportName: 'Test Results',
reportTitles: 'Rubocop Results, Bundler Audit Results, Cucumber Results, RSpec Results, Test Coverage']) reportTitles: 'Cucumber Results, RSpec Results, Test Coverage'])
}
}
stage('Build production deliverable') {
when {
expression {
buildArtifact
}
}
steps {
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
sh ''' #!/usr/local/bin/bash
ZIP_FILE="url-shortener_${BRANCH_NAME}_$(cat VERSION | cut -d"'" -f2).tgz"
CUR_DIR=$(pwd)
bundle config set --local without 'test'
bundle config set --local path "vendor"
bundle install
mkdir -p /tmp/url-shortener
cp -R * /tmp/url-shortener
cp .env.rb /tmp/url-shortener/
cp .ruby-version /tmp/url-shortener/
cd /tmp/url-shortener
rm -rf features spec coverage db/*.db .git* Jenkinsfile *.html .rubocop.yml
cd /tmp
tar -czvf $ZIP_FILE url-shortener/
mv /tmp/$ZIP_FILE $CUR_DIR/
'''
}
archiveArtifacts artifacts: '*.tgz'
}
}
stage('Generate SHA256 Hash and Upload to Artifact Repository') {
environment {
ARTIFACTS_KEY = credentials('artifactor-key')
}
steps {
sshagent(['artifactor-key']) {
sh ''' #!/usr/local/bin/bash
UUID=$(uuidgen -r)
VERSION=$(cat VERSION | cut -d"'" -f2)
BRANCH=${BRANCH_NAME}
ZIP_FILE="url-shortener_${BRANCH_NAME}_$(cat VERSION | cut -d"'" -f2).tgz"
sha256 $ZIP_FILE >> SHA256.sig
ssh artifactor@artifacts mkdir -p projects/url-shortener/$BRANCH/$VERSION/$UUID
rsync SHA256.sig artifactor@artifacts:projects/url-shortener/$BRANCH/$VERSION/$UUID/
rsync $ZIP_FILE artifactor@artifacts:projects/url-shortener/$BRANCH/$VERSION/$UUID/
'''
}
}
}
stage('Clean up deliverable') {
when {
expression {
buildArtifact
}
}
steps {
sh 'rm -rf /tmp/url-shortener'
sh 'rm SHA256.sig'
} }
} }
} }
@ -162,8 +57,5 @@ pipeline {
failure { failure {
mattermostSend channel: 'git-messages', color: 'danger', message: "[${JOB_NAME}](${JOB_URL}) [#${BUILD_NUMBER}](${BUILD_URL}) ([Gitea](${GIT_URL}))", text: "Build Failed" mattermostSend channel: 'git-messages', color: 'danger', message: "[${JOB_NAME}](${JOB_URL}) [#${BUILD_NUMBER}](${BUILD_URL}) ([Gitea](${GIT_URL}))", text: "Build Failed"
} }
always {
cleanWs deleteDirs: true, patterns: [[pattern: '*', type: 'INCLUDE']]
}
} }
} }

View File

@ -1,11 +1,11 @@
# simple url shortener # simple url shortener
this project is a simple solution to build out a URL shortener using this project is a simple solution to build out a URL shortener using
Roda, Puma, Sequel, and SQLite on a FreeBSD Jail Roda, Falcon, Sequel, and SQLite
the point of this project is to quickly build something and work on continuous deployment while making small refinements to the functional pieces. the point of this project is to quickly build something and work on continuous deployment while making small refinements to the functional pieces.
the outside pieces of software that this project relies on are rbenv, ruby-build, sqlite3, and pkgconf. the outside pieces of software that this project relies on are sqlite3 and pkgconf.
@ -14,9 +14,9 @@ first you have to install the dependencies:
`bundle install` `bundle install`
if you want to skip the test dependencies: if you want the development group included run this first:
`bundle config set --local without 'test'` `bundle config set --local with 'development'`
then create a .env.rb file in the root directory that contains the following ENV attributes: then create a .env.rb file in the root directory that contains the following ENV attributes:
@ -24,19 +24,15 @@ then create a .env.rb file in the root directory that contains the following ENV
``` ```
ENV["APP_SESSION_SECRET"] = {output of a random 64 byte secret} ENV["APP_SESSION_SECRET"] = {output of a random 64 byte secret}
ENV["DB_NAME"] = {DB_NAME} ENV["DB_NAME"] = {db file name}
``` ```
after the dependencies are installed, you have to create the db and schema after the dependencies are installed, you have to create the db
`sequel -m db/migrations sqlite://db/{DB_NAME}` `sequel -m db/migrations sqlite://db/{DB_NAME}`
to start the application with Puma: to start the application with Falcon:
`rackup -o {ip address} -p {port}` `rackup -o {ip address} -p {port} -s falcon`
to run it as a daemon:
`rackup -o {ip address} -p {port} -D`

View File

@ -1 +0,0 @@
VERSION = '1.0.1'

63
app.rb
View File

@ -1,12 +1,9 @@
# frozen_string_literal: true
require 'roda' require 'roda'
require 'securerandom' require 'securerandom'
require 'json' require 'json'
require 'sequel' require 'sequel'
require 'open-uri' require 'open-uri'
# URL Shortener App class
class App < Roda class App < Roda
plugin :sessions, secret: ENV.delete('APP_SESSION_SECRET') plugin :sessions, secret: ENV.delete('APP_SESSION_SECRET')
plugin :render, escape: true plugin :render, escape: true
@ -14,92 +11,92 @@ class App < Roda
plugin :json_parser plugin :json_parser
plugin :request_headers plugin :request_headers
DB = Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}") DB = Sequel.sqlite("db/#{ENV['DB_NAME']}")
links = DB[:links] links = DB[:links]
route do |r| route do |r|
r.root do r.root do
@message = flash['message'] || 'Enter a URL' @message = flash['message'] || "Enter a URL"
view :home view :home
end end
r.get String do | url_code | r.get String do | url_code |
link = links.filter(code: url_code) link = links.filter(:code => url_code)
r.redirect link.first[:url] unless link.first.nil? r.redirect link.first[:url] unless link.first.nil?
@message = "Link #{url_code} doesn't exist" @message = "Link #{url_code} doesn't exist"
response.status = 404 response.status = 404
view :home view :home
end end
r.post 'create' do r.post "create" do
url = r.params['url'] url = r.params['url']
if url.nil? || url.empty? if url.nil? or url.empty?
flash['message'] = 'Please enter a valid URL' flash['message'] = "Please enter a valid URL";
r.redirect '/' r.redirect '/'
end end
begin begin
OpenURI.open_uri(url) OpenURI.open_uri(url)
rescue URI::BadURIError rescue URI::BadURIError
flash['message'] = 'Invalid URL' flash['message'] = "Invalid URL"
r.redirect '/' r.redirect '/'
rescue OpenURI::HTTPError rescue OpenURI::HTTPError
flash['message'] = 'URL not found' flash['message'] = "URL not found"
r.redirect '/' r.redirect '/'
rescue SocketError rescue SocketError => e
flash['message'] = 'URL does not resolve' flash['message'] = "URL does not resolve"
r.redirect '/' r.redirect '/'
end end
if links.filter(url:).first.nil? if links.filter(:url => url).first.nil?
code = SecureRandom.urlsafe_base64 4 code = SecureRandom.urlsafe_base64 4
links.insert(url:, code:) links.insert(url: url, code: code)
@message = 'Link created' @message = "Link created"
end end
code = links.filter(url:).first[:code] code = links.filter(:url => url).first[:code]
@message ||= 'Link exists' @message ||= "Link exists"
@new_link = "http://#{request.env['HTTP_HOST']}/#{code}" @new_link = 'http://' + request.env['HTTP_HOST'] + '/' + code
view :create view :create
end end
r.on 'links' do r.on "links" do
r.post do r.post do
if r.headers['CONTENT_TYPE'] != 'application/json' if 'application/json' != r.headers['CONTENT_TYPE']
response.status = 400 response.status = 400
return { message: 'not a valid json request' }.to_json return {message: "not a valid json request"}.to_json
end end
url = r.params['url'] url = r.params['url']
if url.nil? if url.nil?
response.status = 400 response.status = 400
return { message: 'missing url parameter' }.to_json return {message: "missing url parameter"}.to_json
end end
if url.empty? if url.empty?
response.status = 400 response.status = 400
return { message: 'invalid url parameter' }.to_json return {message: "invalid url parameter"}.to_json
end end
begin begin
OpenURI.open_uri(url) OpenURI.open_uri(url)
rescue URI::BadURIError rescue URI::BadURIError
response.status = 400 response.status = 400
return { message: 'invalid url parameter' }.to_json return {message: "invalid url parameter"}.to_json
rescue OpenURI::HTTPError rescue OpenURI::HTTPError
response.status = 400 response.status = 400
return { message: 'url not found' }.to_json return {message: "url not found"}.to_json
rescue SocketError rescue SocketError => e
response.status = 400 response.status = 400
return { message: 'url does not resolve' }.to_json return {message: "url does not resolve"}.to_json
end end
if links.filter(url:).first.nil? if links.filter(:url => url).first.nil?
code = SecureRandom.urlsafe_base64 4 code = SecureRandom.urlsafe_base64 4
links.insert(url:, code:) links.insert(url: url, code: code)
end end
code = links.filter(url:).first[:code] code = links.filter(:url => url).first[:code]
@new_link = "http://#{request.env['HTTP_HOST']}/#{code}" @new_link = 'http://' + request.env['HTTP_HOST'] + '/' + code
return { url:, code:, link: @new_link }.to_json return {url: url, code: code, link: @new_link}.to_json
end end
end end
end end

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
require './.env' require './.env'
require './app' require './app'

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
Sequel.migration do Sequel.migration do
change do change do
create_table :links do create_table :links do

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true
# BEFORE # BEFORE
Before('@db-test') do Before('@db-test') do
@links = Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}")[:links] @links = Sequel.sqlite("db/#{ENV['DB_NAME']}")[:links]
end end
# GIVEN # GIVEN
Given('I visit the {string} page') do |string| Given('I visit the {string} page') do |string|
@ -12,13 +11,14 @@ Given('I visit the {string} page') do |string|
end end
Given('A link already exists with the url {string}') do |string| Given('A link already exists with the url {string}') do |string|
@links.insert(url: string, code: 'aaaaaa') @links.insert(url: string, code: "aaaaaa")
end end
Given('A link already exists with the url {string} and code {string}') do |url, code| Given('A link already exists with the url {string} and code {string}') do |url, code|
@links.insert(url:, code:) @links.insert(url: url, code: code)
end end
# WHEN # WHEN
When('I click the {string} button') do |string| When('I click the {string} button') do |string|
@ -33,6 +33,7 @@ When('I visit the {string} location') do |string|
visit string visit string
end end
# THEN # THEN
Then('I should see text {string}') do |string| Then('I should see text {string}') do |string|
@ -65,6 +66,7 @@ Then('The status code should be {int}') do |code|
page.status_code.should eq(code) page.status_code.should eq(code)
end end
# AFTER # AFTER
After('@db-test') do After('@db-test') do

View File

@ -1,27 +1,25 @@
# frozen_string_literal: true
require 'simplecov' require 'simplecov'
SimpleCov.start SimpleCov.start
require_relative '../../.env' require_relative '../../.env'
ENV['DB_NAME'] = "test_#{ENV.fetch('DB_NAME', nil)}" ENV["DB_NAME"] = "test_#{ENV["DB_NAME"]}"
require_relative '../../app' require_relative '../../app'
require 'rubygems' require 'rubygems'
require 'roda' require 'roda'
require 'sequel' require 'sequel'
require 'capybara' require 'capybara'
require 'capybara/dsl' require 'capybara/dsl'
require 'capybara/cucumber'
require 'rspec' require 'rspec'
###
require 'rspec/expectations'
require 'rspec/matchers'
# DB initialization # DB initialization
Sequel.extension :migration Sequel.extension :migration
Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}") do |db| Sequel.sqlite("db/#{ENV['DB_NAME']}") do |db|
Sequel::Migrator.apply(db, 'db/migrations') Sequel::Migrator.apply(db, "db/migrations")
end end
# attach app to Capybara # attach app to Capybara
Capybara.app = App Capybara.app = App
include Capybara::DSL
include RSpec::Expectations
include RSpec::Matchers

2
secret.rb 100644
View File

@ -0,0 +1,2 @@
require 'securerandom'
puts SecureRandom.base64(64).inspect()

View File

@ -1,10 +1,8 @@
# frozen_string_literal: true
require 'simplecov' require 'simplecov'
SimpleCov.start SimpleCov.start
require_relative '../.env' require_relative '../.env'
ENV['DB_NAME'] = "test_#{ENV.fetch('DB_NAME', nil)}" ENV["DB_NAME"] = "test_#{ENV["DB_NAME"]}"
require_relative '../app' require_relative '../app'
require 'rubygems' require 'rubygems'
require 'roda' require 'roda'
@ -14,27 +12,27 @@ require 'rack/test'
# DB initialization # DB initialization
Sequel.extension :migration Sequel.extension :migration
Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}") do |db| Sequel.sqlite("db/#{ENV['DB_NAME']}") do |db|
Sequel::Migrator.apply(db, 'db/migrations') Sequel::Migrator.apply(db, "db/migrations")
end end
def app def app
App App
end end
describe 'Submit API request to create new link' do describe "Submit API request to create new link" do
include Rack::Test::Methods include Rack::Test::Methods
before :each do before :each do
@links = Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}")[:links] @links = Sequel.sqlite("db/#{ENV['DB_NAME']}")[:links]
end end
after :each do after :each do
@links.delete @links.delete
end end
it 'should return link data in json format when a valid url is submitted' do it "should return link data in json format when a valid url is submitted" do
data = { data = {
url: 'http://google.com' url: 'http://google.com'
} }
post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json') post('/links', data.to_json, "CONTENT_TYPE" => "application/json")
expect(last_response).to be_ok expect(last_response).to be_ok
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['url']).to eq(data[:url]) expect(response_json['url']).to eq(data[:url])
@ -46,15 +44,16 @@ describe 'Submit API request to create new link' do
data = { data = {
url: '' url: ''
} }
post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json') post('/links', data.to_json, "CONTENT_TYPE" => "application/json")
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('invalid url parameter') expect(response_json['message']).to eq('invalid url parameter')
end end
it "should return with a 400 status and 'missing url parameter' message when an empty url is submitted" do it "should return with a 400 status and 'missing url parameter' message when an empty url is submitted" do
data = {} data = {
post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json') }
post('/links', data.to_json, "CONTENT_TYPE" => "application/json")
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('missing url parameter') expect(response_json['message']).to eq('missing url parameter')
@ -64,7 +63,7 @@ describe 'Submit API request to create new link' do
data = { data = {
url: 'not-an-url' url: 'not-an-url'
} }
post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json') post('/links', data.to_json, "CONTENT_TYPE" => "application/json")
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('invalid url parameter') expect(response_json['message']).to eq('invalid url parameter')
@ -74,7 +73,7 @@ describe 'Submit API request to create new link' do
data = { data = {
url: 'http://google.com/example' url: 'http://google.com/example'
} }
post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json') post('/links', data.to_json, "CONTENT_TYPE" => "application/json")
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('url not found') expect(response_json['message']).to eq('url not found')
@ -84,14 +83,13 @@ describe 'Submit API request to create new link' do
data = { data = {
url: 'http://bad.tld' url: 'http://bad.tld'
} }
post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json') post('/links', data.to_json, "CONTENT_TYPE" => "application/json")
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('url does not resolve') expect(response_json['message']).to eq('url does not resolve')
end end
it "should return with a 400 status and 'not a valid json request' it "should return with a 400 status and 'not a valid json request' message when a request is made with the wrong content type header" do
message when a request is made with the wrong content type header" do
data = { data = {
url: 'http://google.com' url: 'http://google.com'
} }