Remove old integration tests (#1207)

* Remove old integration tests

* Move new_integration to integration

We removed legacy integration tests, so we're free to retake the word
This commit is contained in:
Marc Busqué 2022-09-01 06:08:08 +02:00 committed by GitHub
parent aac7fd5e5c
commit be2cbce1ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
126 changed files with 1 additions and 7139 deletions

View File

@ -8,7 +8,7 @@ require "hanami/devtools/rake_tasks"
namespace :spec do
RSpec::Core::RakeTask.new(:unit) do |task|
file_list = FileList["spec/**/*_spec.rb"]
file_list = file_list.exclude("spec/{integration,isolation}/**/*_spec.rb")
file_list = file_list.exclude("spec/{isolation}/**/*_spec.rb")
task.pattern = file_list
end

View File

@ -1,84 +0,0 @@
# frozen_string_literal: true
RSpec.describe "App middleware stack", type: :integration do
it "mounts Rack middleware" do
with_project do
generate "action web home#index --url=/"
generate_middleware
# Add apps/web/middleware to the load paths
replace "apps/web/app.rb", "load_paths << [", "load_paths << ['middleware',"
# Require Rack::ETag
unshift "apps/web/app.rb", "require 'rack/etag'"
# Mount middleware
replace "apps/web/app.rb", "# middleware.use", "middleware.use 'Web::Middleware::Runtime'\nmiddleware.use 'Web::Middleware::Custom', 'OK'\nmiddleware.use Rack::ETag"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = "OK"
end
end
end
EOF
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["X-Runtime"]).to eq("1ms")
expect(last_response.headers["X-Custom"]).to eq("OK")
expect(last_response.headers["ETag"]).to_not be_nil
end
end
end
private
def generate_middleware # rubocop:disable Metrics/MethodLength
write "apps/web/middleware/runtime.rb", <<~EOF
module Web
module Middleware
class Runtime
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers["X-Runtime"] = "1ms"
[status, headers, body]
end
end
end
end
EOF
write "apps/web/middleware/custom.rb", <<~EOF
module Web
module Middleware
class Custom
def initialize(app, value)
@app = app
@value = value
end
def call(env)
status, headers, body = @app.call(env)
headers["X-Custom"] = @value
[status, headers, body]
end
end
end
end
EOF
end
end

View File

@ -1,48 +0,0 @@
# frozen_string_literal: true
RSpec.describe "assets", type: :integration do
describe "CDN mode" do
it "servers assets with CDN url" do
with_project do
generate "action web home#index --url=/"
write "apps/web/assets/javascripts/app.css", <<~EOF
body { color: #333 };
EOF
rewrite "apps/web/templates/app.html.erb", <<~EOF
<!DOCTYPE html>
<html>
<head>
<title>Web</title>
<%= favicon %>
<%= stylesheet 'app' %>
</head>
<body>
<%= yield %>
</body>
</html>
EOF
replace_last "apps/web/app.rb", "# scheme 'https'", "scheme 'https'"
replace "apps/web/app.rb", "# host 'cdn.example.org'", "host 'cdn.example.org'"
replace_last "apps/web/app.rb", "# port 443", "port 443"
#
# Precompile
#
RSpec::Support::Env["HANAMI_ENV"] = "production"
RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
RSpec::Support::Env["SMTP_HOST"] = "localhost"
RSpec::Support::Env["SMTP_PORT"] = "25"
hanami "assets precompile"
server do
visit "/"
expect(page.body).to include(%(<link href="https://cdn.example.org/assets/app-5df86b4e9cbd733a027762b2f6bf8693.css" type="text/css" rel="stylesheet" integrity="sha256-LxaTcWkL8TAWFQWeHJ7OqoSoEXXaYapNIS+TCvGNf48=" crossorigin="anonymous">))
end
end
end
end
end

View File

@ -1,42 +0,0 @@
# frozen_string_literal: true
RSpec.describe "assets", type: :integration do
describe "fingerprint mode" do
it "servers assets with fingerprint url" do
with_project do
generate "action web home#index --url=/"
write "apps/web/assets/javascripts/app.css", <<~EOF
body { color: #333 };
EOF
rewrite "apps/web/templates/app.html.erb", <<~EOF
<!DOCTYPE html>
<html>
<head>
<title>Web</title>
<%= favicon %>
<%= stylesheet 'app' %>
</head>
<body>
<%= yield %>
</body>
</html>
EOF
#
# Precompile
#
RSpec::Support::Env["HANAMI_ENV"] = "production"
RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
RSpec::Support::Env["SMTP_HOST"] = "localhost"
RSpec::Support::Env["SMTP_PORT"] = "25"
hanami "assets precompile"
server do
visit "/"
expect(page.body).to include(%(<link href="/assets/app-5df86b4e9cbd733a027762b2f6bf8693.css" type="text/css" rel="stylesheet" integrity="sha256-LxaTcWkL8TAWFQWeHJ7OqoSoEXXaYapNIS+TCvGNf48=" crossorigin="anonymous">))
end
end
end
end
end

View File

@ -1,50 +0,0 @@
# frozen_string_literal: true
RSpec.describe "assets", type: :integration do
describe "helpers" do
it "renders assets tags" do
with_project do
generate "action web home#index --url=/"
rewrite "apps/web/templates/app.html.erb", <<~EOF
<!DOCTYPE html>
<html>
<head>
<title>Web</title>
<%= favicon %>
<%= stylesheet 'app' %>
</head>
<body>
<%= yield %>
<%= javascript 'app' %>
</body>
</html>
EOF
rewrite "apps/web/templates/home/index.html.erb", <<~EOF
<%= image('app.jpg') %>
<%= video('movie.mp4') %>
<%=
video do
text "Your browser does not support the video tag"
source src: view.asset_path('movie.mp4'), type: 'video/mp4'
source src: view.asset_path('movie.ogg'), type: 'video/ogg'
end
%>
EOF
server do
visit "/"
expect(page.body).to include(%(<link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">))
expect(page.body).to include(%(<link href="/assets/app.css" type="text/css" rel="stylesheet">))
expect(page.body).to include(%(<script src="/assets/app.js" type="text/javascript"></script>))
expect(page.body).to include(%(<img src="/assets/app.jpg" alt="App">))
expect(page.body).to include(%(<video src="/assets/movie.mp4"></video>))
expect(page.body).to include(%(<video>\nYour browser does not support the video tag\n<source src="/assets/movie.mp4" type="video/mp4">\n<source src="/assets/movie.ogg" type="video/ogg">\n</video>))
end
end
end
end
end

View File

@ -1,70 +0,0 @@
# frozen_string_literal: true
RSpec.describe "assets", type: :integration do
describe "serve" do
it "compiles and serves assets in development mode" do
project = "bookshelf_serve_assets"
with_project(project, gems: ['sassc']) do
generate "action web home#index --url=/"
write "apps/web/assets/javascripts/app.css.sass", <<~EOF
$font-family: Helvetica, sans-serif
body
font: 100% $font-family
EOF
rewrite "apps/web/templates/app.html.erb", <<~EOF
<!DOCTYPE html>
<html>
<head>
<title>Web</title>
<%= favicon %>
<%= stylesheet 'app' %>
</head>
<body>
<%= yield %>
</body>
</html>
EOF
server do
visit "/"
expect(page.body).to include(%(<link href="/assets/app.css" type="text/css" rel="stylesheet">))
visit "/assets/app.css"
expect(page.body).to include(%(body {\n font: 100% Helvetica, sans-serif; }\n))
end
end
end
it "serve assets with prefixes" do
with_project do
generate "action web home#index --url=/"
replace(
"apps/web/app.rb",
"# Specify sources for assets",
"prefix '/library/assets'\n# Specify sources for assets"
)
replace(
"apps/web/config/routes.rb",
"/",
"namespace :library { get '/', to: 'home#index' }"
)
write "apps/web/assets/javascripts/app.js", <<~EOF
console.log('test');
EOF
hanami "assets precompile"
server do
visit "/library/assets/app.js"
expect(page).to have_content("console.log('test');")
end
end
end
end
end

View File

@ -1,54 +0,0 @@
# frozen_string_literal: true
require "json"
RSpec.describe "assets", type: :integration do
describe "subresource integrity" do
it "precompiles assets with checksums calculated with given algorithms" do
with_project do
generate "action web home#index --url=/"
write "apps/web/assets/javascripts/app.css", <<~EOF
body { font: Helvetica; }
EOF
rewrite "apps/web/templates/app.html.erb", <<~EOF
<!DOCTYPE html>
<html>
<head>
<title>Web</title>
<%= favicon %>
<%= stylesheet 'app' %>
</head>
<body>
<%= yield %>
</body>
</html>
EOF
replace "apps/web/app.rb", "subresource_integrity :sha256", "subresource_integrity :sha256, :sha512"
#
# Precompile
#
RSpec::Support::Env["HANAMI_ENV"] = "production"
# FIXME: database connection shouldn't be required for `assets precompile`
RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
RSpec::Support::Env["SMTP_HOST"] = "localhost"
RSpec::Support::Env["SMTP_PORT"] = "25"
hanami "assets precompile"
manifest = File.read("public/assets.json")
expect(JSON.parse(manifest)).to be_kind_of(Hash) # assert it's a well-formed JSON
expect(manifest).to include(%("/assets/app.css":{"target":"/assets/app-068e7d97b3671a14824bc20437ac5d06.css","sri":["sha256-cHgODQTP2nNk9ER+ViDGp+lC+ddHRmuLgJk05glJ4+w=","sha512-TDyxo1Ow7UjBib6ykALJh7J1OHEcE0yX4X21s1714ZBAhwdOz7k9t8+QQDAwWAmeH97bNaZGY7oTfVrwyTQ3cw=="]}))
expect(manifest).to include(%("/assets/favicon.ico":{"target":"/assets/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico","sri":["sha256-PLEDhpDsTBpxl1KtXjzBjg+PUG67zpf05B1z2db4iJU=","sha512-9NvaW7aVAywbLPPi3fw5s4wtWwd37i8VpzgfEo9uCOyr/mDT2dbYJtGNjfTJa4R8TOw69yHr4NhazPQsLt1WHw=="]}))
server do
visit "/"
expect(page.body).to include(%(<link href="/assets/app-068e7d97b3671a14824bc20437ac5d06.css" type="text/css" rel="stylesheet" integrity="sha256-cHgODQTP2nNk9ER+ViDGp+lC+ddHRmuLgJk05glJ4+w= sha512-TDyxo1Ow7UjBib6ykALJh7J1OHEcE0yX4X21s1714ZBAhwdOz7k9t8+QQDAwWAmeH97bNaZGY7oTfVrwyTQ3cw==" crossorigin="anonymous">))
end
end
end
end
end

View File

@ -1,50 +0,0 @@
# frozen_string_literal: true
RSpec.describe "body parsers", type: :integration do
it "parses JSON payload for non-GET requests" do
with_project do
generate_action
enable_json_body_parser
server do
post "/books", %({"book": {"title": "CLI apps with Ruby"}}), "CONTENT_TYPE" => "app/json", "HTTP_ACCEPT" => "app/json"
expect(last_response.body).to eq(%({"book":{"title":"CLI apps with Ruby"}}))
end
end
end
it "doesn't eval untrusted JSON" do
with_project do
generate_action
enable_json_body_parser
server do
post "/books", %({"json_class": "Foo"}), "CONTENT_TYPE" => "app/json", "HTTP_ACCEPT" => "app/json"
expect(last_response.body).to eq(%({"json_class":"Foo"}))
end
end
end
private
def generate_action
generate "action web books#create --url=/books --method=POST"
rewrite "apps/web/controllers/books/create.rb", <<~EOF
require 'hanami/utils/json'
module Web::Controllers::Books
class Create
include Web::Action
def call(params)
self.body = Hanami::Utils::Json.generate(params.to_hash)
end
end
end
EOF
end
def enable_json_body_parser
inject_line_after "apps/web/app.rb", "configure do", "body_parsers :json"
end
end

View File

@ -1,147 +0,0 @@
# frozen_string_literal: true
require "json"
RSpec.describe "hanami assets", type: :integration do
describe "precompile" do
it "precompiles assets" do
gems = %w[sassc coffee-script]
Platform.match do
os(:linux).engine(:ruby) { gems.push("therubyracer") }
os(:linux).engine(:jruby) { gems.push("therubyrhino") }
end
with_project("bookshelf_assets_precompile", gems: gems) do
#
# Web assets
#
write "apps/web/assets/javascripts/app.js.coffee", <<~EOF
class App
constructor: () ->
@init = true
EOF
write "apps/web/assets/stylesheets/_colors.scss", <<~EOF
$background-color: #f5f5f5;
EOF
write "apps/web/assets/stylesheets/app.css.scss", <<~EOF
@import 'colors';
body {
background-color: $background-color;
}
EOF
#
# Admin assets
#
generate "app admin"
write "apps/admin/assets/javascripts/dashboard.js.coffee", <<~EOF
class Dashboard
constructor: (@data) ->
EOF
#
# Precompile
#
RSpec::Support::Env["HANAMI_ENV"] = "production"
hanami "assets precompile"
# rubocop:disable Lint/ImplicitStringConcatenation
#
# Verify manifest
#
manifest = retry_exec(Errno::ENOENT) do
File.read("public/assets.json")
end
expect(JSON.parse(manifest)).to be_kind_of(Hash) # assert it's a well-formed JSON
expect(manifest).to include(%("/assets/admin/dashboard.js":{"target":"/assets/admin/dashboard-39744f9626a70683b6c2d46305798883.js","sri":["sha256-1myPVWoqrq+uAVP2DSkmAown+5dm0x61+E3AjlGOKEc="]}))
expect(manifest).to include(%("/assets/admin/favicon.ico":{"target":"/assets/admin/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico","sri":["sha256-PLEDhpDsTBpxl1KtXjzBjg+PUG67zpf05B1z2db4iJU="]}))
expect(manifest).to include(%("/assets/app.css":{"target":"/assets/app-adb4104884aadde9abfef0bd98ac461e.css","sri":["sha256-S6V565W2In9pWE0uzMASpp58xCg32TN3at3Fv4g9aRA="]}))
expect(manifest).to include(%("/assets/app.js":{"target":"/assets/app-bb8f10498d83d401db238549409dc4c5.js","sri":["sha256-9m4OTbWigbDPp4oCe1LZz9isqidvW1c3jNL6mXMj2xs="]}))
expect(manifest).to include(%("/assets/favicon.ico":{"target":"/assets/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico","sri":["sha256-PLEDhpDsTBpxl1KtXjzBjg+PUG67zpf05B1z2db4iJU="]}))
#
# Verify web assets (w/ checksum)
#
expect("public/assets/app-adb4104884aadde9abfef0bd98ac461e.css").to have_file_content <<~EOF
body {background-color: #f5f5f5}
EOF
expect("public/assets/app-bb8f10498d83d401db238549409dc4c5.js").to have_file_content \
"""
(function(){var App;App=(function(){function App(){this.init=true;}
return App;})();}).call(this);
"""
expect("public/assets/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico").to be_an_existing_file
#
# Verify web assets (w/o checksum)
#
expect("public/assets/app.css").to have_file_content <<~EOF
body {background-color: #f5f5f5}
EOF
expect("public/assets/app.js").to have_file_content \
"""
(function(){var App;App=(function(){function App(){this.init=true;}
return App;})();}).call(this);
"""
expect("public/assets/favicon.ico").to be_an_existing_file
#
# Verify admin assets (w/ checksum)
#
expect("public/assets/admin/dashboard-39744f9626a70683b6c2d46305798883.js").to have_file_content \
"""
(function(){var Dashboard;Dashboard=(function(){function Dashboard(data){this.data=data;}
return Dashboard;})();}).call(this);
"""
expect("public/assets/admin/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico").to be_an_existing_file
#
# Verify admin assets (w/o checksum)
#
expect("public/assets/admin/dashboard.js").to have_file_content \
"""
(function(){var Dashboard;Dashboard=(function(){function Dashboard(data){this.data=data;}
return Dashboard;})();}).call(this);
"""
expect("public/assets/admin/favicon.ico").to be_an_existing_file
# rubocop:enable Lint/ImplicitStringConcatenation
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami assets precompile
Usage:
hanami assets precompile
Description:
Precompile assets for deployment
Options:
--help, -h # Print this help
Examples:
hanami assets precompile # Basic usage
hanami assets precompile HANAMI_ENV=production # Precompile assets for production environment
OUT
run_cmd "hanami assets precompile --help", output
end
end
end
end

View File

@ -1,14 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami assets", type: :integration do
it "prints subcommands" do
with_project do
output = <<~OUT
Commands:
hanami assets precompile # Precompile assets for deployment
OUT
run_cmd "hanami assets", output, exit_status: 1
end
end
end

View File

@ -1,105 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami console", type: :integration do
context "irb" do
it "starts console" do
project_name = "bookshelf_console_irb"
with_project(project_name, console: :irb) do
setup_model
console do |input, _, _|
input.puts("Hanami::VERSION")
input.puts("Web::App")
input.puts("Web.routes")
input.puts("BookRepository.new.all.to_a")
input.puts("exit")
end
expect(out).to include(Hanami::VERSION)
expect(out).to include("Web::App")
expect(out).to include("#<Hanami::Routes")
expect(out).to include("[]")
expect(out).to include("[#{project_name}] [INFO]")
expect(out).to include("SELECT `id`, `title` FROM `books` ORDER BY `books`.`id`")
end
end
it "starts console with --engine option" do
with_project("bookshelf_console_irb", console: :irb) do
console(" --engine=irb") do |input, _, _|
input.puts("Hanami::VERSION")
input.puts("exit")
end
expect(out).to include(Hanami::VERSION)
end
end
it "starts console without hanami-model" do
project_without_hanami_model("bookshelf", gems: ["dry-struct"], console: :irb) do
write "lib/entities/access_token.rb", <<~EOF
require 'dry-struct'
require 'securerandom'
module Types
include Dry::Types.module
end
class AccessToken < Dry::Struct
attribute :id, Types::String.default { SecureRandom.uuid }
attribute :secret, Types::String
attribute :digest, Types::String
end
EOF
console do |input, _, _|
input.puts("AccessToken.new(id: '1', secret: 'shh', digest: 'def')")
input.puts("exit")
end
expect(out).to include('#<AccessToken id="1" secret="shh" digest="def">')
end
end
end # irb
xit "returns error when known engine isn't bundled" do
with_project("bookshelf_console_irb", console: :irb) do
output = "Missing gem for `pry' console engine. Please make sure to add it to `Gemfile'."
run_cmd "hanami console --engine=pry", output, exit_status: 1
end
end
it "returns error when unknown engine is requested" do
with_project("bookshelf_console_irb", console: :irb) do
output = "Unknown console engine: `foo'."
run_cmd "hanami console --engine=foo", output, exit_status: 1
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami console
Usage:
hanami console
Description:
Starts Hanami console
Options:
--engine=VALUE # Force a specific console engine: (pry/ripl/irb)
--help, -h # Print this help
Examples:
hanami console # Uses the bundled engine
hanami console --engine=pry # Force to use Pry
OUT
run_cmd "hanami console --help", output
end
end
# TODO: test with pry
# TODO: test with ripl
end

View File

@ -1,74 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
describe "apply" do
it "migrates, dumps structure, deletes migrations", if: RUBY_VERSION < '2.4' do
with_project do
versions = generate_migrations
hanami "db apply"
hanami "db version"
expect(out).to include(versions.last.to_s)
db = Pathname.new("db")
schema = db.join("schema.sql").to_s
migrations = db.join("migrations")
expect(schema).to have_file_content <<~SQL
CREATE TABLE `schema_migrations` (`filename` varchar(255) NOT NULL PRIMARY KEY);
CREATE TABLE `users` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255), `age` integer);
INSERT INTO "schema_migrations" VALUES('#{versions.first}_create_users.rb');
INSERT INTO "schema_migrations" VALUES('#{versions.last}_add_age_to_users.rb');
SQL
expect(migrations.children).to be_empty
end
end
it "migrates, dumps structure, deletes migrations", if: RUBY_VERSION >= '2.4' do
with_project do
versions = generate_migrations
hanami "db apply"
hanami "db version"
expect(out).to include(versions.last.to_s)
db = Pathname.new('db')
schema = db.join('schema.sql').to_s
migrations = db.join('migrations')
expect(schema).to have_file_content <<-SQL
CREATE TABLE `schema_migrations` (`filename` varchar(255) NOT NULL PRIMARY KEY);
CREATE TABLE `users` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255), `age` integer);
CREATE TABLE sqlite_sequence(name,seq);
INSERT INTO schema_migrations VALUES('#{versions.first}_create_users.rb');
INSERT INTO schema_migrations VALUES('#{versions.last}_add_age_to_users.rb');
SQL
expect(migrations.children).to be_empty
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami db apply
Usage:
hanami db apply
Description:
Migrate, dump the SQL schema, and delete the migrations (experimental)
Options:
--help, -h # Print this help
OUT
run_cmd "hanami db apply --help", output
end
end
end
end

View File

@ -1,40 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
describe "console" do
it "starts database console" do
with_project do
generate_migrations
hanami "db prepare"
db_console do |input, _, _|
input.puts('INSERT INTO users (id, name, age) VALUES(1, "Luca", 34);')
input.puts("SELECT * FROM users;")
input.puts(".quit")
end
expect(out).to include("1|Luca|34")
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami db console
Usage:
hanami db console
Description:
Starts a database console
Options:
--help, -h # Print this help
OUT
run_cmd "hanami db console --help", output
end
end
end
end

View File

@ -1,50 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
describe "create" do
it "creates database" do
project = "bookshelf_db_create"
with_project(project) do
hanami "db create"
db = Pathname.new("db").join("#{project}_development.sqlite").to_s
expect(db).to be_an_existing_file
end
end
it "doesn't create in production" do
project = "bookshelf_db_create_production"
with_project(project) do
RSpec::Support::Env["HANAMI_ENV"] = "production"
hanami "db create"
expect(exitstatus).to eq(1)
db = Pathname.new("db").join("#{project}.sqlite").to_s
expect(db).to_not be_an_existing_file
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami db create
Usage:
hanami db create
Description:
Create the database (only for development/test)
Options:
--help, -h # Print this help
OUT
run_cmd 'hanami db create --help', output
end
end
end
end

View File

@ -1,54 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
describe "drop" do
it "drops database" do
project = "bookshelf_db_drop"
with_project(project) do
db = Pathname.new("db").join("#{project}_development.sqlite").to_s
hanami "db create"
expect(db).to be_an_existing_file
hanami "db drop"
expect(db).to_not be_an_existing_file
end
end
it "doesn't drop in production" do
project = "bookshelf_db_drop_production"
with_project(project) do
RSpec::Support::Env["HANAMI_ENV"] = "production"
db = Pathname.new("db").join("#{project}.sqlite").to_s
FileUtils.touch(db) # simulate existing database
hanami "db drop"
expect(exitstatus).to eq(1)
expect(db).to be_an_existing_file
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami db drop
Usage:
hanami db drop
Description:
Drop the database (only for development/test)
Options:
--help, -h # Print this help
OUT
run_cmd 'hanami db drop --help', output
end
end
end
end

View File

@ -1,108 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
describe "migrate" do
it "migrates database" do
project = "bookshelf_db_migrate"
with_project(project) do
generate_migrations
hanami "db create"
hanami "db migrate"
db = Pathname.new("db").join("#{project}_development.sqlite")
users = `sqlite3 #{db} ".schema users"`
expect(users).to include("CREATE TABLE `users` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255), `age` integer);")
version = `sqlite3 #{db} "SELECT filename FROM schema_migrations ORDER BY filename DESC LIMIT 1"`
expect(version).to include("add_age_to_users")
end
end
it "migrates database up to a version" do
project = "bookshelf_db_migrate_up_to_version"
with_project(project) do
versions = generate_migrations
hanami "db create"
hanami "db migrate #{versions.first}"
db = Pathname.new("db").join("#{project}_development.sqlite")
users = `sqlite3 #{db} ".schema users"`
expect(users).to include("CREATE TABLE `users` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255));")
version = `sqlite3 #{db} "SELECT filename FROM schema_migrations ORDER BY filename DESC LIMIT 1"`
expect(version).to include("create_users")
end
end
it "migrates database down to a version" do
project = "bookshelf_db_migrate_down_to_version"
with_project(project) do
versions = generate_migrations
hanami "db create"
hanami "db migrate" # up to latest version
hanami "db migrate #{versions.first}"
db = Pathname.new("db").join("#{project}_development.sqlite")
users = `sqlite3 #{db} ".schema users"`
expect(users).to include("CREATE TABLE `users`(`id` integer DEFAULT (NULL) NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255) DEFAULT (NULL) NULL);")
version = `sqlite3 #{db} "SELECT filename FROM schema_migrations ORDER BY filename DESC LIMIT 1"`
expect(version).to include("create_users")
end
end
it "migrates database down to 0" do
project = "bookshelf_db_migrate_down_to_zero"
with_project(project) do
generate_migrations
hanami "db create"
hanami "db migrate" # up to latest version
hanami "db migrate 0"
db = Pathname.new("db").join("#{project}_development.sqlite")
users = `sqlite3 #{db} ".schema users"`
expect(users).to eq("")
version = `sqlite3 #{db} "SELECT filename FROM schema_migrations ORDER BY filename DESC LIMIT 1"`
expect(version).to eq("")
end
end
it "prints help message" do
with_project do
banner = <<~OUT
Command:
hanami db drop
Usage:
hanami db drop
Description:
Drop the database (only for development/test)
Options:
--help, -h # Print this help
OUT
output = [
banner,
# %r{ hanami db migrate [\d]{14} # Migrate to a specific version}
]
run_cmd 'hanami db drop --help', output
end
end
end
end

View File

@ -1,36 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
describe "prepare" do
it "prepares database" do
with_project do
versions = generate_migrations
hanami "db prepare"
hanami "db version"
expect(out).to include(versions.last.to_s)
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami db prepare
Usage:
hanami db prepare
Description:
Drop, create, and migrate the database (only for development/test)
Options:
--help, -h # Print this help
OUT
run_cmd 'hanami db prepare --help', output
end
end
end
end

View File

@ -1,96 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
describe "rollback" do
it "rollbacks database" do
project = "bookshelf_db_rollback"
with_project(project) do
generate_migrations
hanami "db create"
hanami "db migrate"
hanami "db rollback"
db = Pathname.new("db").join("#{project}_development.sqlite")
users = `sqlite3 #{db} ".schema users"`
expect(users).to include("CREATE TABLE `users`(`id` integer DEFAULT (NULL) NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255) DEFAULT (NULL) NULL);")
version = `sqlite3 #{db} "SELECT filename FROM schema_migrations ORDER BY filename DESC LIMIT 1"`
expect(version).to_not include("add_age_to_users")
end
end
it "rollbacks database using custom steps" do
project = "bookshelf_db_migrate_custom_steps"
with_project(project) do
generate_migrations
hanami "db create"
hanami "db migrate"
hanami "db rollback 2"
db = Pathname.new("db").join("#{project}_development.sqlite")
users = `sqlite3 #{db} ".schema users"`
expect(users).to_not include("CREATE TABLE `users`(`id` integer DEFAULT (NULL) NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255) DEFAULT (NULL) NULL);")
version = `sqlite3 #{db} "SELECT filename FROM schema_migrations ORDER BY filename DESC LIMIT 1"`
expect(version).to_not include("create_users")
end
end
it "returns an error when steps isn't an integer" do
with_project do
generate_migrations
hanami "db create"
hanami "db migrate"
output = "the number of steps must be a positive integer (you entered `quindici')."
run_cmd "hanami db rollback quindici", output, exit_status: 1
end
end
it "returns an error when steps is 0" do
with_project do
generate_migrations
hanami "db create"
hanami "db migrate"
output = "the number of steps must be a positive integer (you entered `0')."
run_cmd "hanami db rollback 0", output, exit_status: 1
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami db rollback
Usage:
hanami db rollback [STEPS]
Description:
Rollback migrations
Arguments:
STEPS # Number of steps to rollback the database
Options:
--help, -h # Print this help
Examples:
hanami db rollback # Rollbacks latest migration
hanami db rollback 2 # Rollbacks last two migration
OUT
run_cmd 'hanami db rollback --help', output
end
end
end
end

View File

@ -1,38 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
describe "version" do
it "prints database version" do
with_project do
versions = generate_migrations
hanami "db create"
hanami "db migrate"
hanami "db version"
expect(out).to include(versions.last.to_s)
expect(out).to_not include("SELECT * FROM")
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami db version
Usage:
hanami db version
Description:
Print the current migrated version
Options:
--help, -h # Print this help
OUT
run_cmd 'hanami db version --help', output
end
end
end
end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami db", type: :integration do
it "prints subcommands" do
with_project do
output = <<~OUT
Commands:
hanami db apply # Migrate, dump the SQL schema, and delete the migrations (experimental)
hanami db console # Starts a database console
hanami db create # Create the database (only for development/test)
hanami db drop # Drop the database (only for development/test)
hanami db migrate [VERSION] # Migrate the database
hanami db prepare # Drop, create, and migrate the database (only for development/test)
hanami db rollback [STEPS] # Rollback migrations
hanami db version # Print the current migrated version
OUT
run_cmd "hanami db", output, exit_status: 1
end
end
end

View File

@ -1,143 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami destroy", type: :integration do
describe "action" do
it "destroys action" do
with_project do
generate "action web books#index"
output = [
"subtract apps/web/config/routes.rb",
"remove spec/web/views/books/index_spec.rb",
"remove apps/web/templates/books/index.html.erb",
"remove apps/web/views/books/index.rb",
"remove apps/web/controllers/books/index.rb",
"remove spec/web/controllers/books/index_spec.rb"
]
run_cmd "hanami destroy action web books#index", output
expect("spec/web/controllers/books/index_spec.rb").to_not be_an_existing_file
expect("apps/web/controllers/books/index.rb").to_not be_an_existing_file
expect("apps/web/views/books/index.rb").to_not be_an_existing_file
expect("apps/web/templates/books/index.html.erb").to_not be_an_existing_file
expect("spec/web/views/books/index_spec.rb").to_not be_an_existing_file
expect("apps/web/config/routes.rb").to_not have_file_content(%r{get '/books', to: 'books#index'})
end
end
it "destroys namespaced action" do
with_project do
generate "action web api/books#index"
output = [
"subtract apps/web/config/routes.rb",
"remove spec/web/views/api/books/index_spec.rb",
"remove apps/web/templates/api/books/index.html.erb",
"remove apps/web/views/api/books/index.rb",
"remove apps/web/controllers/api/books/index.rb",
"remove spec/web/controllers/api/books/index_spec.rb"
]
run_cmd "hanami destroy action web api/books#index", output
expect("spec/web/controllers/api/books/index_spec.rb").to_not be_an_existing_file
expect("apps/web/controllers/api/books/index.rb").to_not be_an_existing_file
expect("apps/web/views/api/books/index.rb").to_not be_an_existing_file
expect("apps/web/templates/api/books/index.html.erb").to_not be_an_existing_file
expect("spec/web/views/api/books/index_spec.rb").to_not be_an_existing_file
expect("apps/web/config/routes.rb").to_not have_file_content(%r{get '/api/books', to: 'api/books#index'})
end
end
it "destroys action without view" do
with_project do
generate "action web home#ping --skip-view --url=/ping"
output = [
"subtract apps/web/config/routes.rb",
"remove apps/web/controllers/home/ping.rb",
"remove spec/web/controllers/home/ping_spec.rb"
]
run_cmd "hanami destroy action web home#ping", output
expect("spec/web/controllers/home/ping_spec.rb").to_not be_an_existing_file
expect("apps/web/controllers/home/ping.rb").to_not be_an_existing_file
expect("apps/web/views/home/ping.rb").to_not be_an_existing_file
expect("apps/web/templates/home/ping.html.erb").to_not be_an_existing_file
expect("spec/web/views/home/ping_spec.rb").to_not be_an_existing_file
expect("apps/web/config/routes.rb").to_not have_file_content(%r{get '/ping', to: 'home#ping'})
end
end
it "fails with missing arguments" do
with_project do
output = <<~OUT
ERROR: "hanami destroy action" was called with no arguments
Usage: "hanami destroy action APP ACTION"
OUT
run_cmd "hanami destroy action", output, exit_status: 1
end
end
it "fails with missing app" do
with_project("bookshelf_generate_action_without_app") do
output = <<~OUT
ERROR: "hanami destroy action" was called with arguments ["home#index"]
Usage: "hanami destroy action APP ACTION"
OUT
run_cmd "hanami destroy action home#index", output, exit_status: 1
end
end
it "fails with unknown app" do
with_project("bookshelf_generate_action_with_unknown_app") do
output = "`foo' is not a valid APP. Please specify one of: `web'"
run_cmd "hanami destroy action foo home#index", output, exit_status: 1
end
end
it "fails with unknown action" do
with_project("bookshelf_generate_action_with_unknown_action") do
output = <<~OUT
cannot find `home#index' in `web' app.
please run `hanami routes' to know the existing actions.
OUT
run_cmd "hanami destroy action web home#index", output, exit_status: 1
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami destroy action
Usage:
hanami destroy action APP ACTION
Description:
Destroy an action from app
Arguments:
APP # REQUIRED The app name (eg. `web`)
ACTION # REQUIRED The action name (eg. `home#index`)
Options:
--help, -h # Print this help
Examples:
hanami destroy action web home#index # Basic usage
hanami destroy action admin users#index # Destroy from `admin` app
OUT
run_cmd 'hanami destroy action --help', output
end
end
end # action
end

View File

@ -1,118 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami destroy", type: :integration do
describe "app" do
it "destroys app" do
with_project do
generate "app admin"
output = [
"subtract .env.test",
"subtract .env.development",
"subtract config/environment.rb",
"subtract config/environment.rb",
"remove spec/admin",
"remove apps/admin"
]
run_cmd "hanami destroy app admin", output
expect(".env.test").to_not have_file_content(%r{ADMIN_SESSIONS_SECRET})
expect(".env.development").to_not have_file_content(%r{ADMIN_SESSIONS_SECRET})
expect("config/environment.rb").to_not have_file_content(%r{mount Admin::App})
expect("config/environment.rb").to_not have_file_content("require_relative '../apps/admin/app'")
expect("public/assets/admin").to_not be_an_existing_path
expect("public/assets.json").to_not be_an_existing_path
expect("spec/admin").to_not be_an_existing_path
expect("apps/admin").to_not be_an_existing_path
end
end
it "destroys app with actions and assets" do
with_project do
generate "app api --app-base-url=/api/v1"
generate "action api home#index"
asset = File.join("apps", "api", "assets", "javascripts", "app.js")
touch asset
hanami "assets precompile"
output = [
"subtract .env.test",
"subtract .env.development",
"subtract config/environment.rb",
"subtract config/environment.rb",
"remove public/assets/api/v1",
"remove public/assets.json",
"remove spec/api",
"remove apps/api"
]
run_cmd "hanami destroy app api", output
expect(".env.test").to_not have_file_content(%r{API_SESSIONS_SECRET})
expect(".env.development").to_not have_file_content(%r{API_SESSIONS_SECRET})
expect("config/environment.rb").to_not have_file_content(%r{mount Api::App})
expect("config/environment.rb").to_not have_file_content("require_relative '../apps/api/app'")
expect("public/assets/api/v1").to_not be_an_existing_path
expect("public/assets.json").to_not be_an_existing_path
expect("spec/api").to_not be_an_existing_path
expect("apps/api").to_not be_an_existing_path
end
end
it "fails with missing argument" do
with_project do
output = <<-OUT
ERROR: "hanami destroy app" was called with no arguments
Usage: "hanami destroy app APP"
OUT
run_cmd "hanami destroy app", output, exit_status: 1
end
end
it "fails with unknown app" do
with_project do
output = <<-OUT
`unknown' is not a valid APP. Please specify one of: `web'
OUT
run_cmd "hanami destroy app unknown", output, exit_status: 1
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami destroy app
Usage:
hanami destroy app APP
Description:
Destroy an app
Arguments:
APP # REQUIRED The app name (eg. `web`)
Options:
--help, -h # Print this help
Examples:
hanami destroy app admin # Destroy `admin` app
OUT
run_cmd 'hanami destroy app --help', output
end
end
end # app
end

View File

@ -1,74 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami destroy", type: :integration do
describe "mailer" do
context "destroy a mailer" do
let(:output) do
["remove spec/bookshelf_generate_mailer/mailers/welcome_spec.rb",
"remove lib/bookshelf_generate_mailer/mailers/templates/welcome.html.erb",
"remove lib/bookshelf_generate_mailer/mailers/templates/welcome.txt.erb",
"remove lib/bookshelf_generate_mailer/mailers/welcome.rb"]
end
it "generate the mailer files" do
with_project("bookshelf_generate_mailer", test: "rspec") do
generate "mailer welcome"
run_cmd "hanami destroy mailer welcome", output
expect("spec/bookshelf_generate_mailer/mailers/welcome_spec.rb").to_not be_an_existing_file
expect("lib/bookshelf_generate_mailer/mailers/templates/welcome.html.erb").to_not be_an_existing_file
expect("lib/bookshelf_generate_mailer/mailers/templates/welcome.txt.erb").to_not be_an_existing_file
expect("lib/bookshelf_generate_mailer/mailers/welcome.rb").to_not be_an_existing_file
end
end
end
it "fails with missing arguments" do
with_project("bookshelf_generate_mailer_without_args") do
output = <<~OUT
ERROR: "hanami generate mailer" was called with no arguments
Usage: "hanami generate mailer MAILER"
OUT
run_cmd "hanami generate mailer", output, exit_status: 1
end
end
it "fails with unknown mailer" do
with_project do
output = <<~OUT
cannot find `unknown' mailer. Please have a look at `lib/bookshelf/mailers' directory to find an existing mailer.
OUT
run_cmd "hanami destroy mailer unknown", output, exit_status: 1
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami destroy mailer
Usage:
hanami destroy mailer MAILER
Description:
Destroy a mailer
Arguments:
MAILER # REQUIRED The mailer name (eg. `welcome`)
Options:
--help, -h # Print this help
Examples:
hanami destroy mailer welcome # Destroy `WelcomeMailer` mailer
OUT
run_cmd 'hanami destroy mailer --help', output
end
end
end
end

View File

@ -1,70 +0,0 @@
# frozen_string_literal: true
require "pathname"
RSpec.describe "hanami destroy", type: :integration do
describe "migration" do
it "destroys migration" do
with_project do
migration = Pathname.new("db").join("migrations", "20170127165331_create_users.rb").to_s
File.open(migration, "wb+") { |f| f.write("migration") }
output = [
"remove #{migration}"
]
run_cmd "hanami destroy migration create_users", output
expect(migration).to_not be_an_existing_file
end
end
it "fails with missing argument" do
with_project do
output = <<~OUT
ERROR: "hanami destroy migration" was called with no arguments
Usage: "hanami destroy migration MIGRATION"
OUT
run_command "hanami destroy migration", output, exit_status: 1
end
end
it "fails with unknown migration" do
with_project do
output = <<~OUT
cannot find `create_unknowns'. Please have a look at `db/migrations' directory to find an existing migration
OUT
run_command "hanami destroy migration create_unknowns", output, exit_status: 1
end
end
it "prints help message" do
with_project do
banner = <<~OUT
Command:
hanami destroy migration
Usage:
hanami destroy migration MIGRATION
Description:
Destroy a migration
Arguments:
MIGRATION # REQUIRED The migration name (eg. `create_users`)
Options:
--help, -h # Print this help
Examples:
OUT
output = [
banner,
%r{ hanami destroy migration create_users # Destroy `db/migrations/[\d]{14}_create_users.rb`}
]
run_command "hanami destroy migration --help", output
end
end
end # migration
end

View File

@ -1,113 +0,0 @@
# frozen_string_literal: true
require "pathname"
RSpec.describe "hanami destroy", type: :integration do
describe "model" do
it "destroys model" do
with_project do
generate "model user"
migration = Dir.glob(Pathname.new("db").join("migrations", "*_create_users.rb")).first.to_s
output = [
"remove spec/bookshelf/repositories/user_repository_spec.rb",
"remove spec/bookshelf/entities/user_spec.rb",
"remove lib/bookshelf/repositories/user_repository.rb",
"remove lib/bookshelf/entities/user.rb"
]
run_cmd "hanami destroy model user", output
expect(migration).to be_an_existing_file
expect("lib/bookshelf/entities/user.rb").to_not be_an_existing_file
expect("lib/bookshelf/repositories/user_repository.rb").to_not be_an_existing_file
expect("spec/bookshelf/entities/user_spec.rb").to_not be_an_existing_file
expect("spec/bookshelf/repositories/user_repository_spec.rb").to_not be_an_existing_file
end
end
it "destroys model even if migration was deleted manually" do
with_project do
generate "model user"
migration = Dir.glob(Pathname.new("db").join("migrations", "*_create_users.rb")).first.to_s
run_simple "rm #{migration}"
expect(migration).to_not be_an_existing_file
output = [
"remove spec/bookshelf/repositories/user_repository_spec.rb",
"remove spec/bookshelf/entities/user_spec.rb",
"remove lib/bookshelf/repositories/user_repository.rb",
"remove lib/bookshelf/entities/user.rb"
]
run_cmd "hanami destroy model user", output
end
end
it "fails with missing argument" do
with_project do
output = <<~OUT
ERROR: "hanami destroy model" was called with no arguments
Usage: "hanami destroy model MODEL"
OUT
run_cmd "hanami destroy model", output, exit_status: 1
end
end
xit "prints help message" do
with_project do
output = <<~OUT
Usage:
hanami destroy model NAME
Description:
`hanami destroy model` will destroy an entity along with repository and \n corresponding tests
> $ hanami destroy model car
OUT
run_cmd 'hanami destroy model --help', output
end
end
it "fails with unknown model" do
with_project do
output = <<~OUT
cannot find `unknown' model. Please have a look at `lib/bookshelf/entities' directory to find an existing model.
OUT
run_cmd "hanami destroy model unknown", output, exit_status: 1
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami destroy model
Usage:
hanami destroy model MODEL
Description:
Destroy a model
Arguments:
MODEL # REQUIRED The model name (eg. `user`)
Options:
--help, -h # Print this help
Examples:
hanami destroy model user # Destroy `User` entity and `UserRepository` repository
OUT
run_cmd 'hanami destroy model --help', output
end
end
end # model
end

View File

@ -1,18 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami destroy", type: :integration do
it "prints subcommands" do
with_project do
output = <<~OUT
Commands:
hanami destroy action APP ACTION # Destroy an action from app
hanami destroy app APP # Destroy an app
hanami destroy mailer MAILER # Destroy a mailer
hanami destroy migration MIGRATION # Destroy a migration
hanami destroy model MODEL # Destroy a model
OUT
run_cmd "hanami destroy", output, exit_status: 1
end
end
end

View File

@ -1,469 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami generate", type: :integration do
describe "action" do
it "generates action" do
with_project("bookshelf_generate_action") do
output = [
"create spec/web/controllers/authors/index_spec.rb",
"create apps/web/controllers/authors/index.rb",
"create apps/web/views/authors/index.rb",
"create apps/web/templates/authors/index.html.erb",
"create spec/web/views/authors/index_spec.rb",
"insert apps/web/config/routes.rb"
]
run_cmd "hanami generate action web authors#index", output
#
# apps/web/controllers/authors/index.rb
#
expect("apps/web/controllers/authors/index.rb").to have_file_content <<~END
module Web
module Controllers
module Authors
class Index
include Web::Action
def call(params)
end
end
end
end
end
END
#
# apps/web/views/authors/index.rb
#
expect("apps/web/views/authors/index.rb").to have_file_content <<~END
module Web
module Views
module Authors
class Index
include Web::View
end
end
end
end
END
#
# apps/web/config/routes.rb
#
expect("apps/web/config/routes.rb").to have_file_content(%r{get '/authors', to: 'authors#index'})
end
end
it "generates namespaced action" do
with_project("bookshelf_generate_action") do
output = [
"create spec/web/controllers/api/authors/index_spec.rb",
"create apps/web/controllers/api/authors/index.rb",
"create apps/web/views/api/authors/index.rb",
"create apps/web/templates/api/authors/index.html.erb",
"create spec/web/views/api/authors/index_spec.rb",
"insert apps/web/config/routes.rb"
]
run_cmd "hanami generate action web api/authors#index", output
#
# apps/web/controllers/api/authors/index.rb
#
expect("apps/web/controllers/api/authors/index.rb").to have_file_content <<~END
module Web
module Controllers
module Api
module Authors
class Index
include Web::Action
def call(params)
end
end
end
end
end
end
END
#
# apps/web/views/api/authors/index.rb
#
expect("apps/web/views/api/authors/index.rb").to have_file_content <<~END
module Web
module Views
module Api
module Authors
class Index
include Web::View
end
end
end
end
end
END
#
# apps/web/config/routes.rb
#
expect("apps/web/config/routes.rb").to have_file_content(%r{get '/api/authors', to: 'api/authors#index'})
end
end
it "generates non-RESTful actions" do
with_project do
run_cmd "hanami generate action web sessions#sign_out"
#
# apps/web/config/routes.rb
#
expect("apps/web/config/routes.rb").to have_file_content(%r{get '/sessions/sign_out', to: 'sessions#sign_out'})
end
end
it "fails with missing arguments" do
with_project("bookshelf_generate_action_without_args") do
output = <<~OUT
ERROR: "hanami generate action" was called with no arguments
Usage: "hanami generate action APP ACTION"
OUT
run_cmd "hanami generate action", output, exit_status: 1
end
end
it "fails with missing app" do
with_project("bookshelf_generate_action_without_app") do
output = <<~OUT
ERROR: "hanami generate action" was called with arguments ["home#index"]
Usage: "hanami generate action APP ACTION"
OUT
run_cmd "hanami generate action home#index", output, exit_status: 1
end
end
it "fails with unknown app" do
with_project("bookshelf_generate_action_with_unknown_app") do
output = "`foo' is not a valid APP. Please specify one of: `web'"
run_cmd "hanami generate action foo home#index", output, exit_status: 1
end
end
context "--url" do
it "generates action" do
with_project("bookshelf_generate_action_url") do
output = [
"insert apps/web/config/routes.rb"
]
run_cmd "hanami generate action web home#index --url=/", output
#
# apps/web/config/routes.rb
#
expect("apps/web/config/routes.rb").to have_file_content(%r{get '/', to: 'home#index'})
end
end
it "fails with missing argument" do
with_project("bookshelf_generate_action_missing_url") do
output = "`' is not a valid URL"
run_cmd "hanami generate action web books#create --url=", output, exit_status: 1
end
end
end
context "--skip-view" do
it "generates action" do
with_project("bookshelf_generate_action_skip_view") do
output = [
"create apps/web/controllers/status/check.rb",
"create spec/web/controllers/status/check_spec.rb",
"insert apps/web/config/routes.rb"
]
run_cmd "hanami generate action web status#check --skip-view", output
#
# apps/web/controllers/status/check.rb
#
expect("apps/web/controllers/status/check.rb").to have_file_content <<~END
module Web
module Controllers
module Status
class Check
include Web::Action
def call(params)
self.body = 'OK'
end
end
end
end
end
END
end
end
it "generates namespaced action" do
with_project("bookshelf_generate_action_skip_view") do
output = [
"create apps/web/controllers/api/authors/index.rb",
"create spec/web/controllers/api/authors/index_spec.rb",
"insert apps/web/config/routes.rb"
]
run_cmd "hanami generate action web api/authors#index --skip-view", output
#
# apps/web/controllers/status/check.rb
#
expect("apps/web/controllers/api/authors/index.rb").to have_file_content <<~END
module Web
module Controllers
module Api
module Authors
class Index
include Web::Action
def call(params)
self.body = 'OK'
end
end
end
end
end
end
END
end
end
end
context "--method" do
it "generates action" do
with_project("bookshelf_generate_action_method") do
output = [
"insert apps/web/config/routes.rb"
]
run_cmd "hanami generate action web books#create --method=POST", output
#
# apps/web/config/routes.rb
#
expect("apps/web/config/routes.rb").to have_file_content(%r{post '/books', to: 'books#create'})
end
end
it "fails with missing argument" do
with_project("bookshelf_generate_action_missing_method") do
output = "`' is not a valid HTTP method. Please use one of: `GET' `POST' `PUT' `DELETE' `HEAD' `OPTIONS' `TRACE' `PATCH' `OPTIONS' `LINK' `UNLINK'"
run_cmd "hanami generate action web books#create --method=", output, exit_status: 1
end
end
it "fails with unknown argument" do
with_project('bookshelf_generate_action_unknown_method') do
output = "`FOO' is not a valid HTTP method. Please use one of: `GET' `POST' `PUT' `DELETE' `HEAD' `OPTIONS' `TRACE' `PATCH' `OPTIONS' `LINK' `UNLINK'"
run_cmd "hanami generate action web books#create --method=FOO", output, exit_status: 1
end
end
end
context "erb" do
it "generates action" do
with_project("bookshelf_generate_action_erb", template: "erb") do
output = [
"create apps/web/templates/books/index.html.erb"
]
run_cmd "hanami generate action web books#index", output
#
# apps/web/templates/books/index.html.erb
#
expect("apps/web/templates/books/index.html.erb").to have_file_content <<~END
END
#
# spec/web/views/books/index_spec.rb
#
expect("spec/web/views/books/index_spec.rb").to have_file_content %r{'apps/web/templates/books/index.html.erb'}
end
end
end # erb
context "haml" do
it "generates action" do
with_project("bookshelf_generate_action_haml", template: "haml") do
output = [
"create apps/web/templates/books/index.html.haml"
]
run_cmd "hanami generate action web books#index", output
#
# apps/web/templates/books/index.html.haml
#
expect("apps/web/templates/books/index.html.haml").to have_file_content <<~END
END
#
# spec/web/views/books/index_spec.rb
#
expect("spec/web/views/books/index_spec.rb").to have_file_content(%r{'apps/web/templates/books/index.html.haml'})
end
end
end # haml
context "slim" do
it "generates action" do
with_project("bookshelf_generate_action_slim", template: "slim") do
output = [
"create apps/web/templates/books/index.html.slim"
]
run_cmd "hanami generate action web books#index", output
#
# apps/web/templates/books/index.html.slim
#
expect("apps/web/templates/books/index.html.slim").to have_file_content <<~END
END
#
# spec/web/views/books/index_spec.rb
#
expect("spec/web/views/books/index_spec.rb").to have_file_content %r{'apps/web/templates/books/index.html.slim'}
end
end
end # slim
context "minitest" do
it "generates action" do
with_project("bookshelf_generate_action_minitest", test: "minitest") do
output = [
"create spec/web/controllers/books/index_spec.rb",
"create spec/web/views/books/index_spec.rb"
]
run_cmd "hanami generate action web books#index", output
#
# spec/web/controllers/books/index_spec.rb
#
expect("spec/web/controllers/books/index_spec.rb").to have_file_content <<~END
require_relative '../../../spec_helper'
describe Web::Controllers::Books::Index do
let(:action) { Web::Controllers::Books::Index.new }
let(:params) { Hash[] }
it 'is successful' do
response = action.call(params)
_(response[0]).must_equal 200
end
end
END
#
# spec/web/views/books/index_spec.rb
#
expect("spec/web/views/books/index_spec.rb").to have_file_content <<~END
require_relative '../../../spec_helper'
describe Web::Views::Books::Index do
let(:exposures) { Hash[format: :html] }
let(:template) { Hanami::View::Template.new('apps/web/templates/books/index.html.erb') }
let(:view) { Web::Views::Books::Index.new(template, exposures) }
let(:rendered) { view.render }
it 'exposes #format' do
_(view.format).must_equal exposures.fetch(:format)
end
end
END
end
end
end # minitest
context "rspec" do
it "generates action" do
with_project("bookshelf_generate_action_rspec", test: "rspec") do
output = [
"create spec/web/controllers/books/index_spec.rb",
"create spec/web/views/books/index_spec.rb"
]
run_cmd "hanami generate action web books#index", output
#
# spec/web/controllers/books/index_spec.rb
#
expect("spec/web/controllers/books/index_spec.rb").to have_file_content <<~END
RSpec.describe Web::Controllers::Books::Index, type: :action do
let(:action) { described_class.new }
let(:params) { Hash[] }
it 'is successful' do
response = action.call(params)
expect(response[0]).to eq 200
end
end
END
#
# spec/web/views/books/index_spec.rb
#
expect("spec/web/views/books/index_spec.rb").to have_file_content <<~END
RSpec.describe Web::Views::Books::Index, type: :view do
let(:exposures) { Hash[format: :html] }
let(:template) { Hanami::View::Template.new('apps/web/templates/books/index.html.erb') }
let(:view) { described_class.new(template, exposures) }
let(:rendered) { view.render }
it 'exposes #format' do
expect(view.format).to eq exposures.fetch(:format)
end
end
END
end
end
end # rspec
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami generate action
Usage:
hanami generate action APP ACTION
Description:
Generate an action for app
Arguments:
APP # REQUIRED The app name (eg. `web`)
ACTION # REQUIRED The action name (eg. `home#index`)
Options:
--url=VALUE # The action URL
--method=VALUE # The action HTTP method
--[no-]skip-view # Skip view and template, default: false
--help, -h # Print this help
Examples:
hanami generate action web home#index # Basic usage
hanami generate action admin home#index # Generate for `admin` app
hanami generate action web home#index --url=/ # Specify URL
hanami generate action web sessions#destroy --method=GET # Specify HTTP method
hanami generate action web books#create --skip-view # Skip view and template
OUT
run_cmd 'hanami generate action --help', output
end
end
end # action
end

View File

@ -1,215 +0,0 @@
# frozen_string_literal: true
require "hanami/utils/string"
RSpec.describe "hanami generate", type: :integration do
describe "app" do
context "with app name" do
it_behaves_like "a new app" do
let(:input) { "admin" }
end
end
context "with underscored app name" do
it_behaves_like "a new app" do
let(:input) { "cool_app" }
end
end
context "with dashed app name" do
it_behaves_like "a new app" do
let(:input) { "awesome-app" }
end
end
context "with camel case app name" do
it_behaves_like "a new app" do
let(:input) { "CaMElAPp" }
end
end
context "without require_relative" do
it "generates app" do
with_project("bookshelf_generate_app_without_require_relative") do
app = "no_req_relative"
app_name = Hanami::Utils::String.new(app).classify
output = [
"insert config/environment.rb"
]
File.write(
"config/environment.rb",
File
.read("config/environment.rb")
.lines
.reject { |l| l[/^require_relative '.*'\n$/] }
.reject { |l| l[/^ mount Web::App, at: '\/'\n$/] }
.join("")
)
run_cmd "hanami generate app #{app}", output
#
# config/environment.rb
#
expect("config/environment.rb").to have_file_content(%r{require_relative '../apps/#{app}/app'})
expect("config/environment.rb").to have_file_content(%r{mount #{app_name}::App, at: '/no_req_relative'})
end
end
end
context "--app-base-url" do
it "generates app" do
with_project("bookshelf_generate_app_app_base_url") do
app = "api"
app_name = Hanami::Utils::String.new(app).classify
output = [
"insert config/environment.rb"
]
run_cmd "hanami generate app #{app} --app-base-url=/api/v1", output
#
# config/environment.rb
#
expect("config/environment.rb").to have_file_content(%r{require_relative '../apps/#{app}/app'})
expect("config/environment.rb").to have_file_content(%r{mount #{app_name}::App, at: '/api/v1'})
end
end
it "fails with missing argument" do
with_project("bookshelf_generate_app_missing_app_base_url") do
output = "`' is not a valid URL"
run_cmd "hanami generate app foo --app-base-url=", output, exit_status: 1
end
end
end
context "erb" do
it "generates app" do
with_project("bookshelf_generate_app_erb", template: :erb) do
app = "admin"
app_name = Hanami::Utils::String.new(app).classify
output = [
"create apps/#{app}/templates/app.html.erb"
]
run_cmd "hanami generate app #{app}", output
#
# apps/admin/templates/app.html.erb
#
expect("apps/admin/templates/app.html.erb").to have_file_content <<~END
<!DOCTYPE html>
<html>
<head>
<title>#{app_name}</title>
<%= favicon %>
</head>
<body>
<%= yield %>
</body>
</html>
END
#
# spec/admin/views/app_layout_spec.rb
#
expect("spec/admin/views/app_layout_spec.rb").to have_file_content(%r{Admin::Views::AppLayout})
end
end
end # erb
context "haml" do
it "generates app" do
with_project("bookshelf_generate_app_haml", template: :haml) do
app = "admin"
app_name = Hanami::Utils::String.new(app).classify
output = [
"create apps/#{app}/templates/app.html.haml"
]
run_cmd "hanami generate app #{app}", output
#
# apps/admin/templates/app.html.haml
#
expect("apps/admin/templates/app.html.haml").to have_file_content <<~END
!!!
%html
%head
%title #{app_name}
= favicon
%body
= yield
END
#
# spec/admin/views/app_layout_spec.rb
#
expect("spec/admin/views/app_layout_spec.rb").to have_file_content(%r{Admin::Views::AppLayout})
end
end
end # haml
context "slim" do
it "generates app" do
with_project("bookshelf_generate_app_slim", template: :slim) do
app = "admin"
app_name = Hanami::Utils::String.new(app).classify
output = [
"create apps/#{app}/templates/app.html.slim"
]
run_cmd "hanami generate app #{app}", output
#
# apps/admin/templates/app.html.slim
#
expect("apps/admin/templates/app.html.slim").to have_file_content <<~END
doctype html
html
head
title
| #{app_name}
= favicon
body
= yield
END
#
# spec/admin/views/app_layout_spec.rb
#
expect("spec/admin/views/app_layout_spec.rb").to have_file_content(%r{Admin::Views::AppLayout})
end
end
end # slim
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami generate app
Usage:
hanami generate app APP
Description:
Generate an app
Arguments:
APP # REQUIRED The app name (eg. `web`)
Options:
--app-base-url=VALUE # The app base URL (eg. `/api/v1`)
--help, -h # Print this help
Examples:
hanami generate app admin # Generate `admin` app
hanami generate app api --app-base-url=/api/v1 # Generate `api` app and mount at `/api/v1`
OUT
run_cmd 'hanami generate app --help', output
end
end
end # app
end

View File

@ -1,189 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami generate", type: :integration do
describe "mailer" do
context "generates a new mailer" do
let(:output) do
["create lib/bookshelf_generate_mailer/mailers/welcome.rb",
"create spec/bookshelf_generate_mailer/mailers/welcome_spec.rb",
"create lib/bookshelf_generate_mailer/mailers/templates/welcome.txt.erb",
"create lib/bookshelf_generate_mailer/mailers/templates/welcome.html.erb"]
end
it 'generate the mailer files' do
with_project('bookshelf_generate_mailer', test: 'rspec') do
run_cmd "hanami generate mailer welcome", output
#
# lib/bookshelf_generate_mailer/mailers/welcome.rb
#
expect("lib/bookshelf_generate_mailer/mailers/welcome.rb").to have_file_content <<~END
module Mailers
class Welcome
include Hanami::Mailer
from '<from>'
to '<to>'
subject 'Hello'
end
end
END
expect("lib/bookshelf_generate_mailer/mailers/templates/welcome.txt.erb").to have_file_content ""
expect("lib/bookshelf_generate_mailer/mailers/templates/welcome.html.erb").to have_file_content ""
end
end
it 'generates a proper minitest file' do
with_project('bookshelf_generate_mailer', test: 'minitest') do
run_cmd "hanami generate mailer welcome", output
#
# spec/bookshelf_generate_mailer/mailers/welcome_spec.rb
#
expect("spec/bookshelf_generate_mailer/mailers/welcome_spec.rb").to have_file_content <<~END
require_relative '../../spec_helper'
describe Mailers::Welcome do
it 'delivers email' do
mail = Mailers::Welcome.deliver
end
end
END
end
end
it 'generates a proper RSpec file' do
with_project('bookshelf_generate_mailer', test: 'rspec') do
run_cmd "hanami generate mailer welcome", output
#
# spec/bookshelf_generate_mailer/mailers/welcome_spec.rb
#
expect("spec/bookshelf_generate_mailer/mailers/welcome_spec.rb").to have_file_content <<~END
RSpec.describe Mailers::Welcome, type: :mailer do
it 'delivers email' do
mail = Mailers::Welcome.deliver
end
end
END
end
end
end
it "generates mailer with options from, to and subject with single quotes" do
with_project("bookshelf_generate_mailer_with_options") do
output = [
"create spec/bookshelf_generate_mailer_with_options/mailers/welcome_spec.rb",
"create lib/bookshelf_generate_mailer_with_options/mailers/welcome.rb",
"create lib/bookshelf_generate_mailer_with_options/mailers/templates/welcome.txt.erb",
"create lib/bookshelf_generate_mailer_with_options/mailers/templates/welcome.html.erb"
]
run_cmd "hanami generate mailer welcome --from=\"'mail@example.com'\" --to=\"'user@example.com'\" --subject=\"'Let\'s start'\"", output
expect("lib/bookshelf_generate_mailer_with_options/mailers/welcome.rb").to have_file_content <<~END
module Mailers
class Welcome
include Hanami::Mailer
from 'mail@example.com'
to 'user@example.com'
subject 'Let\'s start'
end
end
END
end
end
it "generates mailer with options from, to and subject with double quotes" do
with_project("bookshelf_generate_mailer_with_options") do
output = [
"create spec/bookshelf_generate_mailer_with_options/mailers/welcome_spec.rb",
"create lib/bookshelf_generate_mailer_with_options/mailers/welcome.rb",
"create lib/bookshelf_generate_mailer_with_options/mailers/templates/welcome.txt.erb",
"create lib/bookshelf_generate_mailer_with_options/mailers/templates/welcome.html.erb"
]
run_cmd "hanami generate mailer welcome --from='\"mail@example.com\"' --to='\"user@example.com\"' --subject='\"Come on \"Folks\"\"'", output
expect("lib/bookshelf_generate_mailer_with_options/mailers/welcome.rb").to have_file_content <<~END
module Mailers
class Welcome
include Hanami::Mailer
from 'mail@example.com'
to 'user@example.com'
subject 'Come on "Folks"'
end
end
END
end
end
it "generates mailer with options from, to and subject without quotes" do
with_project("bookshelf_generate_mailer_with_options") do
output = [
"create spec/bookshelf_generate_mailer_with_options/mailers/welcome_spec.rb",
"create lib/bookshelf_generate_mailer_with_options/mailers/welcome.rb",
"create lib/bookshelf_generate_mailer_with_options/mailers/templates/welcome.txt.erb",
"create lib/bookshelf_generate_mailer_with_options/mailers/templates/welcome.html.erb"
]
run_cmd "hanami generate mailer welcome --from=mail@example.com --to=user@example.com --subject=Welcome", output
expect("lib/bookshelf_generate_mailer_with_options/mailers/welcome.rb").to have_file_content <<~END
module Mailers
class Welcome
include Hanami::Mailer
from 'mail@example.com'
to 'user@example.com'
subject 'Welcome'
end
end
END
end
end
it "fails with missing arguments" do
with_project("bookshelf_generate_mailer_without_args") do
output = <<~OUT
ERROR: "hanami generate mailer" was called with no arguments
Usage: "hanami generate mailer MAILER"
OUT
run_cmd "hanami generate mailer", output, exit_status: 1
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami generate mailer
Usage:
hanami generate mailer MAILER
Description:
Generate a mailer
Arguments:
MAILER # REQUIRED The mailer name (eg. `welcome`)
Options:
--from=VALUE # The default `from` field of the mail
--to=VALUE # The default `to` field of the mail
--subject=VALUE # The mail subject
--help, -h # Print this help
Examples:
hanami generate mailer welcome # Basic usage
hanami generate mailer welcome --from="noreply@example.com" # Generate with default `from` value
hanami generate mailer announcement --to="users@example.com" # Generate with default `to` value
hanami generate mailer forgot_password --subject="Your password reset" # Generate with default `subject`
OUT
run_cmd 'hanami generate mailer --help', output
end
end
end
end

View File

@ -1,72 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami generate", type: :integration do
describe "migration" do
context "with migration name" do
it_behaves_like "a new migration" do
let(:input) { "users" }
end
end
context "with underscored name" do
it_behaves_like "a new migration" do
let(:input) { "create_users" }
end
end
context "with dashed name" do
it_behaves_like "a new migration" do
let(:input) { "add-verified-at-to-users" }
end
end
context "with camel case app name" do
it_behaves_like "a new migration" do
let(:input) { "AddUniqueIndexUsersEmail" }
end
end
context "with missing argument" do
it "fails" do
with_project('bookshelf_generate_migration_missing_arguments') do
output = <<-END
ERROR: "hanami generate migration" was called with no arguments
Usage: "hanami generate migration MIGRATION"
END
run_cmd "hanami generate migration", output, exit_status: 1
end
end
end
it "prints help message" do
with_project do
banner = <<~OUT
Command:
hanami generate migration
Usage:
hanami generate migration MIGRATION
Description:
Generate a migration
Arguments:
MIGRATION # REQUIRED The migration name (eg. `create_users`)
Options:
--help, -h # Print this help
Examples:
OUT
output = [
banner,
%r{ hanami generate migration create_users # Generate `db/migrations/[\d]{14}_create_users.rb`},
]
run_cmd 'hanami generate migration --help', output
end
end
end # migration
end

View File

@ -1,290 +0,0 @@
# frozen_string_literal: true
require "hanami/utils/string"
RSpec.describe "hanami generate", type: :integration do
describe "model" do
context "with model name" do
it_behaves_like "a new model" do
let(:input) { "user" }
end
end
context "with underscored name" do
it_behaves_like "a new model" do
let(:input) { "discounted_book" }
end
end
context "with dashed name" do
it_behaves_like "a new model" do
let(:input) { "user-event" }
end
end
context "with camel case name" do
it_behaves_like "a new model" do
let(:input) { "VerifiedUser" }
end
end
context "with missing argument" do
it "fails" do
with_project('bookshelf_generate_model_missing_arguments') do
output = <<-END
ERROR: "hanami generate model" was called with no arguments
Usage: "hanami generate model MODEL"
END
run_cmd "hanami generate model", output, exit_status: 1
end
end
end
context "with missing migrations directory" do
it "will create directory and migration" do
with_project do
model_name = "book"
directory = Pathname.new("db").join("migrations")
FileUtils.rm_rf(directory)
run_cmd "hanami generate model #{model_name}"
expect(directory).to be_directory
migration = directory.children.find do |m|
m.to_s.include?(model_name)
end
expect(migration).to_not be(nil)
end
end
end
context "with skip-migration" do
it "doesn't create a migration file" do
model_name = "user"
table_name = "users"
project = "bookshelf_generate_model_skip_migration"
with_project(project) do
run_cmd "hanami generate model #{model_name} --skip-migration"
#
# db/migrations/<timestamp>_create_<models>.rb
#
migrations = Pathname.new("db").join("migrations").children
file = migrations.find do |child|
child.to_s.include?("create_#{table_name}")
end
expect(file).to be_nil, "Expected to not find a migration matching: create_#{table_name}. Found #{file&.to_s}"
end
end
it "doesn't create a migration file when --relation is used" do
model_name = "user"
table_name = "accounts"
project = "bookshelf_generate_model_skip_migration"
with_project(project) do
run_cmd "hanami generate model #{model_name} --skip-migration --relation=#{table_name}"
#
# db/migrations/<timestamp>_create_<models>.rb
#
migrations = Pathname.new("db").join("migrations").children
file = migrations.find do |child|
child.to_s.include?("create_#{table_name}")
end
expect(file).to be_nil, "Expected to not find a migration matching: create_#{table_name}. Found #{file&.to_s}"
end
end
end
context "with relation option" do
let(:project) { "generate_model_with_relation_name" }
let(:model_name) { "stimulus" }
let(:class_name) { "Stimulus" }
let(:relation_name) { "stimuli" }
it "creates correct entity, repository, and migration" do
with_project(project) do
output = [
"create lib/#{project}/entities/#{model_name}.rb",
"create lib/#{project}/repositories/#{model_name}_repository.rb",
/create db\/migrations\/(\d+)_create_#{relation_name}.rb/
]
run_cmd "hanami generate model #{model_name} --relation=#{relation_name}", output
expect("lib/#{project}/repositories/#{model_name}_repository.rb").to have_file_content <<~END
class #{class_name}Repository < Hanami::Repository
self.relation = :#{relation_name}
end
END
migration = Pathname.new("db").join("migrations").children.find do |child|
child.to_s.include?("create_#{relation_name}")
end
expect(migration.to_s).to have_file_content <<~END
Hanami::Model.migration do
change do
create_table :#{relation_name} do
primary_key :id
column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
end
end
end
END
end
end
it "handles CamelCase arguments" do
with_project(project) do
model = "sheep"
relation_name = "black_sheeps"
output = [
"create lib/#{project}/entities/#{model}.rb",
"create lib/#{project}/repositories/#{model}_repository.rb",
/create db\/migrations\/(\d+)_create_#{relation_name}.rb/
]
run_cmd "hanami generate model #{model} --relation=BlackSheeps", output
expect("lib/#{project}/repositories/sheep_repository.rb").to have_file_content <<~END
class SheepRepository < Hanami::Repository
self.relation = :#{relation_name}
end
END
migration = Pathname.new("db").join("migrations").children.find do |child|
child.to_s.include?("create_#{relation_name}")
end
expect(migration.to_s).to have_file_content <<~END
Hanami::Model.migration do
change do
create_table :#{relation_name} do
primary_key :id
column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
end
end
end
END
end
end
it "returns error for blank option" do
with_project(project) do
run_cmd "hanami generate model #{model_name} --relation=", "`' is not a valid relation name", exit_status: 1
end
end
end
context "minitest" do
it "generates model" do
project = "bookshelf_generate_model_minitest"
with_project(project, test: :minitest) do
model = "book"
class_name = Hanami::Utils::String.new(model).classify
output = [
"create spec/#{project}/entities/#{model}_spec.rb",
"create spec/#{project}/repositories/#{model}_repository_spec.rb"
]
run_cmd "hanami generate model #{model}", output
#
# spec/<project>/entities/<model>_spec.rb
#
expect("spec/#{project}/entities/#{model}_spec.rb").to have_file_content <<~END
require_relative '../../spec_helper'
describe #{class_name} do
# place your tests here
end
END
#
# spec/<project>/repositories/<model>_repository_spec.rb
#
expect("spec/#{project}/repositories/#{model}_repository_spec.rb").to have_file_content <<~END
require_relative '../../spec_helper'
describe #{class_name}Repository do
# place your tests here
end
END
end
end
end # minitest
context "rspec" do
it "generates model" do
project = "bookshelf_generate_model_rspec"
with_project(project, test: :rspec) do
model = "book"
class_name = Hanami::Utils::String.new(model).classify
output = [
"create spec/#{project}/entities/#{model}_spec.rb",
"create spec/#{project}/repositories/#{model}_repository_spec.rb"
]
run_cmd "hanami generate model #{model}", output
#
# spec/<project>/entities/<model>_spec.rb
#
expect("spec/#{project}/entities/#{model}_spec.rb").to have_file_content <<~END
RSpec.describe #{class_name}, type: :entity do
# place your tests here
end
END
#
# spec/<project>/repositories/<model>_repository_spec.rb
#
expect("spec/#{project}/repositories/#{model}_repository_spec.rb").to have_file_content <<~END
RSpec.describe BookRepository, type: :repository do
# place your tests here
end
END
end
end
end # rspec
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami generate model
Usage:
hanami generate model MODEL
Description:
Generate a model
Arguments:
MODEL # REQUIRED Model name (eg. `user`)
Options:
--[no-]skip-migration # Skip migration, default: false
--relation=VALUE # Name of the database relation, default: pluralized model name
--help, -h # Print this help
Examples:
hanami generate model user # Generate `User` entity, `UserRepository` repository, and the migration
hanami generate model user --skip-migration # Generate `User` entity and `UserRepository` repository
hanami generate model user --relation=accounts # Generate `User` entity, `UserRepository` and migration to create `accounts` table
OUT
run_cmd 'hanami generate model --help', output
end
end
end # model
end

View File

@ -1,56 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami generate", type: :integration do
describe "secret" do
context "without app name" do
it "prints secret" do
with_project do
generate "secret"
expect(out).to match(/[\w]{64}/)
end
end
end
context "with app name" do
it "prints secret" do
with_project do
generate "secret web"
expect(out).to match(%r{WEB_SESSIONS_SECRET="[\w]{64}"})
end
end
end
it 'prints help message' do
with_project do
banner = <<~OUT
Command:
hanami generate secret
Usage:
hanami generate secret [APP]
Description:
Generate session secret
Arguments:
APP # The app name (eg. `web`)
Options:
--help, -h # Print this help
Examples:
OUT
output = [
banner,
# %r{ hanami generate secret # Prints secret (eg. `[\w]{64}`)},
# %r{ hanami generate secret web # Prints session secret (eg. `WEB_SESSIONS_SECRET=[\w]{64}`)}
]
run_cmd 'hanami generate secret --help', output
end
end
end # secret
end

View File

@ -1,19 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami generate", type: :integration do
it "prints subcommands" do
with_project do
output = <<~OUT
Commands:
hanami generate action APP ACTION # Generate an action for app
hanami generate app APP # Generate an app
hanami generate mailer MAILER # Generate a mailer
hanami generate migration MIGRATION # Generate a migration
hanami generate model MODEL # Generate a model
hanami generate secret [APP] # Generate session secret
OUT
run_cmd "hanami generate", output, exit_status: 1
end
end
end

View File

@ -1,235 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami new", type: :integration do
describe "--database" do
context "postgres" do
it "generates project" do
project = "bookshelf_postgresql"
output = [
"create db/migrations/.gitkeep",
"create db/schema.sql"
]
run_cmd "hanami new #{project} --database=postgres", output
within_project_directory(project) do
#
# .env.development
#
development_url = Platform.match do
engine(:ruby) { "postgresql://localhost/#{project}_development" }
engine(:jruby) { "jdbc:postgresql://localhost/#{project}_development" }
end
expect(".env.development").to have_file_content(%r{DATABASE_URL="#{development_url}"})
#
# .env.test
#
test_url = Platform.match do
engine(:ruby) { "postgresql://localhost/#{project}_test" }
engine(:jruby) { "jdbc:postgresql://localhost/#{project}_test" }
end
expect(".env.test").to have_file_content(%r{DATABASE_URL="#{test_url}"})
#
# Gemfile
#
gem_name = Platform.match do
engine(:ruby) { "pg" }
engine(:jruby) { "jdbc-postgres" }
end
expect("Gemfile").to have_file_content(%r{gem '#{gem_name}'})
#
# config/environment.rb
#
expect("config/environment.rb").to have_file_content(%r{ adapter :sql, ENV.fetch\('DATABASE_URL'\)})
expect("config/environment.rb").to have_file_content(%r{ migrations 'db/migrations'})
expect("config/environment.rb").to have_file_content(%r{ schema 'db/schema.sql'})
#
# db/migrations/.gitkeep
#
expect("db/migrations/.gitkeep").to be_an_existing_file
#
# db/schema.sql
#
expect("db/schema.sql").to be_an_existing_file
#
# .gitignore
#
expect(".gitignore").to have_file_content <<-END
/public/assets*
/tmp
.env.local
.env.*.local
END
end
end
end # postgres
describe "sqlite" do
it "generates project" do
project = "bookshelf_sqlite"
output = [
"create db/migrations/.gitkeep",
"create db/schema.sql"
]
run_cmd "hanami new #{project} --database=sqlite", output
within_project_directory(project) do
#
# .env.development
#
development_url = Platform.match do
engine(:ruby) { "sqlite://db/#{project}_development.sqlite" }
engine(:jruby) { "jdbc:sqlite://db/#{project}_development.sqlite" }
end
expect(".env.development").to have_file_content(%r{DATABASE_URL="#{development_url}"})
#
# .env.test
#
test_url = Platform.match do
engine(:ruby) { "sqlite://db/#{project}_test.sqlite" }
engine(:jruby) { "jdbc:sqlite://db/#{project}_test.sqlite" }
end
expect(".env.test").to have_file_content(%r{DATABASE_URL="#{test_url}"})
#
# Gemfile
#
gem_name = Platform.match do
engine(:ruby) { "sqlite3" }
engine(:jruby) { "jdbc-sqlite3" }
end
expect("Gemfile").to have_file_content(%r{gem '#{gem_name}'})
#
# config/environment.rb
#
expect("config/environment.rb").to have_file_content(%r{ adapter :sql, ENV.fetch\('DATABASE_URL'\)})
expect("config/environment.rb").to have_file_content(%r{ migrations 'db/migrations'})
expect("config/environment.rb").to have_file_content(%r{ schema 'db/schema.sql'})
#
# db/migrations/.gitkeep
#
expect("db/migrations/.gitkeep").to be_an_existing_file
#
# db/schema.sql
#
expect("db/schema.sql").to be_an_existing_file
#
# .gitignore
#
expect(".gitignore").to have_file_content <<-END
/db/*.sqlite
/public/assets*
/tmp
.env.local
.env.*.local
END
end
end
end # sqlite
context "mysql" do
it "generates project" do
project = "bookshelf_mysql"
output = [
"create db/migrations/.gitkeep",
"create db/schema.sql"
]
run_cmd "hanami new #{project} --database=mysql", output
within_project_directory(project) do
#
# .env.development
#
development_url = Platform.match do
engine(:ruby) { "mysql2://localhost/#{project}_development" }
engine(:jruby) { "jdbc:mysql://localhost/#{project}_development" }
end
expect(".env.development").to have_file_content(%r{DATABASE_URL="#{development_url}"})
#
# .env.test
#
test_url = Platform.match do
engine(:ruby) { "mysql2://localhost/#{project}_test" }
engine(:jruby) { "jdbc:mysql://localhost/#{project}_test" }
end
expect(".env.test").to have_file_content(%r{DATABASE_URL="#{test_url}"})
#
# Gemfile
#
gem_name = Platform.match do
engine(:ruby) { "mysql2" }
engine(:jruby) { "jdbc-mysql" }
end
expect("Gemfile").to have_file_content(%r{gem '#{gem_name}'})
#
# config/environment.rb
#
expect("config/environment.rb").to have_file_content(%r{ adapter :sql, ENV.fetch\('DATABASE_URL'\)})
expect("config/environment.rb").to have_file_content(%r{ migrations 'db/migrations'})
expect("config/environment.rb").to have_file_content(%r{ schema 'db/schema.sql'})
#
# db/migrations/.gitkeep
#
expect("db/migrations/.gitkeep").to be_an_existing_file
#
# db/schema.sql
#
expect("db/schema.sql").to be_an_existing_file
#
# .gitignore
#
expect(".gitignore").to have_file_content <<-END
/public/assets*
/tmp
.env.local
.env.*.local
END
end
end
end # mysql
context "missing" do
it "returns error" do
output = "`' is not a valid database engine"
run_cmd "hanami new bookshelf --database=", output, exit_status: 1
end
end # missing
context "unknown" do
it "returns error" do
output = "`foo' is not a valid database engine"
run_cmd "hanami new bookshelf --database=foo", output, exit_status: 1
end
end # unknown
end # database
end

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami new", type: :integration do
describe "--hanami-head" do
it "generates project" do
project = "bookshelf_hanami_head"
run_cmd "hanami new #{project} --hanami-head"
within_project_directory(project) do
#
# Gemfile
#
expect('Gemfile').to have_file_content(%r{gem 'hanami-utils', require: false, git: 'https://github.com/hanami/utils.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami-validations', require: false, git: 'https://github.com/hanami/validations.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami-router', require: false, git: 'https://github.com/hanami/router.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami-controller', require: false, git: 'https://github.com/hanami/controller.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami-view', require: false, git: 'https://github.com/hanami/view.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami-helpers', require: false, git: 'https://github.com/hanami/helpers.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami-mailer', require: false, git: 'https://github.com/hanami/mailer.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami-assets', require: false, git: 'https://github.com/hanami/assets.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami-model', require: false, git: 'https://github.com/hanami/model.git', branch: 'develop'})
expect('Gemfile').to have_file_content(%r{gem 'hanami', git: 'https://github.com/hanami/hanami.git', branch: 'develop'})
end
end
end # hanami-head
end

View File

@ -1,118 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami new", type: :integration do
describe "--template" do
context "erb" do
it "generates project" do
project = "bookshelf_erb"
output = [
"create apps/web/templates/app.html.erb"
]
run_cmd "hanami new #{project} --template=erb", output
within_project_directory(project) do
#
# .hanamirc
#
expect(".hanamirc").to have_file_content(%r{template=erb})
#
# apps/web/templates/app.html.erb
#
expect("apps/web/templates/app.html.erb").to have_file_content <<~END
<!DOCTYPE html>
<html>
<head>
<title>Web</title>
<%= favicon %>
</head>
<body>
<%= yield %>
</body>
</html>
END
end
end
end # erb
context "haml" do
it "generates project" do
project = "bookshelf_erb"
output = [
"create apps/web/templates/app.html.haml"
]
run_cmd "hanami new #{project} --template=haml", output
within_project_directory(project) do
#
# .hanamirc
#
expect(".hanamirc").to have_file_content(%r{template=haml})
#
# apps/web/templates/app.html.haml
#
expect("apps/web/templates/app.html.haml").to have_file_content <<~END
!!!
%html
%head
%title Web
= favicon
%body
= yield
END
end
end
end # haml
context "slim" do
it "generates project" do
project = "bookshelf_erb"
output = [
"create apps/web/templates/app.html.slim"
]
run_cmd "hanami new #{project} --template=slim", output
within_project_directory(project) do
#
# .hanamirc
#
expect(".hanamirc").to have_file_content(%r{template=slim})
#
# apps/web/templates/app.html.slim
#
expect("apps/web/templates/app.html.slim").to have_file_content <<~END
doctype html
html
head
title
| Web
= favicon
body
= yield
END
end
end
end # slim
context "missing" do
it "returns error" do
output = "`' is not a valid template engine. Please use one of: `erb', `haml', `slim'"
run_cmd "hanami new bookshelf --template=", output, exit_status: 1
end
end # missing
context "unknown" do
it "returns error" do
output = "`foo' is not a valid template engine. Please use one of: `erb', `haml', `slim'"
run_cmd "hanami new bookshelf --template=foo", output, exit_status: 1
end
end # unknown
end # template
end

View File

@ -1,274 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami new", type: :integration do
describe "--test" do
context "minitest" do
it "generates project" do
project = "bookshelf_minitest"
output = [
"create spec/spec_helper.rb",
"create spec/features_helper.rb",
"create spec/web/views/app_layout_spec.rb"
]
run_cmd "hanami new #{project} --test=minitest", output
within_project_directory(project) do
#
# .hanamirc
#
expect(".hanamirc").to have_file_content(%r{test=minitest})
#
# spec/spec_helper.rb
#
expect("spec/spec_helper.rb").to have_file_content <<~END
# Require this file for unit tests
ENV['HANAMI_ENV'] ||= 'test'
require_relative '../config/environment'
require 'minitest/autorun'
Hanami.boot
END
#
# spec/features_helper.rb
#
expect("spec/features_helper.rb").to have_file_content <<~END
# Require this file for feature tests
require_relative './spec_helper'
require 'capybara'
require 'capybara/dsl'
Capybara.app = Hanami.app
class MiniTest::Spec
include Capybara::DSL
end
END
#
# spec/<app>/views/app_layout_spec.rb
#
expect("spec/web/views/app_layout_spec.rb").to have_file_content <<-END
require "spec_helper"
describe Web::Views::AppLayout do
let(:layout) { Web::Views::AppLayout.new({ format: :html }, "contents") }
let(:rendered) { layout.render }
it 'contains app name' do
_(rendered).must_include('Web')
end
end
END
end
end
end # minitest
describe "rspec" do
it "generates project" do
project = "bookshelf_rspec"
output = [
"create .rspec",
"create spec/spec_helper.rb",
"create spec/features_helper.rb",
"create spec/support/capybara.rb",
"create spec/web/views/app_layout_spec.rb"
]
run_cmd "hanami new #{project} --test=rspec", output
within_project_directory(project) do
#
# .hanamirc
#
expect(".hanamirc").to have_file_content(%r{test=rspec})
#
# .rspec
#
expect(".rspec").to have_file_content <<~END
--color
--require spec_helper
END
#
# spec/spec_helper.rb
#
expect("spec/spec_helper.rb").to have_file_content <<~END
# Require this file for unit tests
ENV['HANAMI_ENV'] ||= 'test'
require_relative '../config/environment'
Hanami.boot
Hanami::Utils.require!("\#{__dir__}/support")
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
#
# The `.rspec` file also contains a few flags that are not defaults but that
# users commonly want.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.run_all_when_everything_filtered = true
# Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. We recommend
# you configure your source control system to ignore this file.
config.example_status_persistence_file_path = "spec/examples.txt"
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in many cases may
# be too noisy due to issues in dependencies.
config.warnings = false
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = 'doc'
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
=end
end
END
#
# spec/features_helper.rb
#
expect("spec/features_helper.rb").to have_file_content <<~END
# Require this file for feature tests
require_relative './spec_helper'
require 'capybara'
require 'capybara/rspec'
RSpec.configure do |config|
config.include RSpec::FeatureExampleGroup
config.include Capybara::DSL, feature: true
config.include Capybara::RSpecMatchers, feature: true
end
END
#
# spec/support/capybara.rb
#
expect("spec/support/capybara.rb").to have_file_content <<~END
module RSpec
module FeatureExampleGroup
def self.included(group)
group.metadata[:type] = :feature
Capybara.app = Hanami.app
end
end
end
END
#
# spec/<app>/views/app_layout_spec.rb
#
expect("spec/web/views/app_layout_spec.rb").to have_file_content <<~END
require "spec_helper"
RSpec.describe Web::Views::AppLayout, type: :view do
let(:layout) { Web::Views::AppLayout.new({ format: :html }, "contents") }
let(:rendered) { layout.render }
it 'contains app name' do
expect(rendered).to include('Web')
end
end
END
end
end
end # rspec
context "missing" do
it "returns error" do
output = "`' is not a valid test framework. Please use one of: `rspec', `minitest'"
run_cmd "hanami new bookshelf --test=", output, exit_status: 1
end
end # missing
context "unknown" do
it "returns error" do
output = "`foo' is not a valid test framework. Please use one of: `rspec', `minitest'"
run_cmd "hanami new bookshelf --test=foo", output, exit_status: 1
end
end # unknown
end # test
end

View File

@ -1,970 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami new", type: :integration do
it "generates vanilla project" do
project = "bookshelf"
output = <<-OUT
create .hanamirc
create .env.development
create .env.test
create README.md
create Gemfile
create config.ru
create config/boot.rb
create config/environment.rb
create lib/#{project}.rb
create public/.gitkeep
create config/initializers/.gitkeep
create lib/#{project}/entities/.gitkeep
create lib/#{project}/repositories/.gitkeep
create lib/#{project}/mailers/.gitkeep
create lib/#{project}/mailers/templates/.gitkeep
create spec/#{project}/entities/.gitkeep
create spec/#{project}/repositories/.gitkeep
create spec/#{project}/mailers/.gitkeep
create spec/support/.gitkeep
create db/migrations/.gitkeep
create Rakefile
create .rspec
create spec/spec_helper.rb
create spec/features_helper.rb
create spec/support/capybara.rb
create db/schema.sql
create .gitignore
run git init . from "."
create apps/web/app.rb
create apps/web/config/routes.rb
create apps/web/views/app_layout.rb
create apps/web/templates/app.html.erb
create apps/web/assets/favicon.ico
create apps/web/controllers/.gitkeep
create apps/web/assets/images/.gitkeep
create apps/web/assets/javascripts/.gitkeep
create apps/web/assets/stylesheets/.gitkeep
create spec/web/features/.gitkeep
create spec/web/controllers/.gitkeep
create spec/web/views/app_layout_spec.rb
insert config/environment.rb
insert config/environment.rb
append .env.development
append .env.test
OUT
run_cmd "hanami new #{project}", output
within_project_directory(project) do
# Assert it's an initialized Git repository
run_cmd "git status", default_git_branch
#
# .hanamirc
#
expect(".hanamirc").to have_file_content <<~END
project=#{project}
test=rspec
template=erb
END
#
# .env.development
#
expect(".env.development").to have_file_content(%r{# Define ENV variables for development environment})
expect(".env.development").to have_file_content(%r{DATABASE_URL="sqlite://db/#{project}_development.sqlite"})
expect(".env.development").to have_file_content(%r{SERVE_STATIC_ASSETS="true"})
expect(".env.development").to have_file_content(%r{WEB_SESSIONS_SECRET="[\w]{64}"})
#
# .env.test
#
expect(".env.test").to have_file_content(%r{# Define ENV variables for test environment})
expect(".env.test").to have_file_content(%r{DATABASE_URL="sqlite://db/#{project}_test.sqlite"})
expect(".env.test").to have_file_content(%r{SERVE_STATIC_ASSETS="true"})
expect(".env.test").to have_file_content(%r{WEB_SESSIONS_SECRET="[\w]{64}"})
#
# README.md
#
expect("README.md").to have_file_content <<~END
# Bookshelf
Welcome to your new Hanami project!
## Setup
How to run tests:
```
% bundle exec rake
```
How to run the development console:
```
% bundle exec hanami console
```
How to run the development server:
```
% bundle exec hanami server
```
How to prepare (create and migrate) DB for `development` and `test` environments:
```
% bundle exec hanami db prepare
% HANAMI_ENV=test bundle exec hanami db prepare
```
Explore Hanami [guides](https://guides.hanamirb.org/), [API docs](http://docs.hanamirb.org/#{Hanami::VERSION}/), or jump in [chat](http://chat.hanamirb.org) for help. Enjoy! 🌸
END
#
# Gemfile
#
if Platform.match?(engine: :ruby)
expect('Gemfile').to have_file_content <<-END
source 'https://rubygems.org'
gem 'rake'
gem 'hanami', '#{Hanami::Version.gem_requirement}'
gem 'hanami-model', '~> 1.3'
gem 'sqlite3'
group :development do
# Code reloading
# See: https://guides.hanamirb.org/projects/code-reloading
gem 'shotgun', platforms: :ruby
gem 'hanami-webconsole'
end
group :test, :development do
gem 'dotenv', '~> 2.4'
end
group :test do
gem 'rspec'
gem 'capybara'
end
group :production do
# gem 'puma'
end
END
end
if Platform.match?(engine: :jruby)
expect("Gemfile").to have_file_content <<~END
source 'https://rubygems.org'
gem 'rake'
gem 'hanami', '#{Hanami::Version.gem_requirement}'
gem 'hanami-model', '~> 1.3'
gem 'jdbc-sqlite3'
group :test, :development do
gem 'dotenv', '~> 2.4'
end
group :test do
gem 'rspec'
gem 'capybara'
end
group :production do
# gem 'puma'
end
END
end
#
# config.ru
#
expect('config.ru').to have_file_content <<-END
require_relative 'config/environment'
run Hanami.app
END
#
# config/boot.rb
#
expect("config/boot.rb").to have_file_content <<~END
require_relative './environment'
Hanami.boot
END
#
# config/environment.rb
#
expect('config/environment.rb').to have_file_content <<-END
require 'bundler/setup'
require 'hanami/setup'
require 'hanami/model'
require_relative '../lib/#{project}'
require_relative '../apps/web/app'
Hanami.configure do
mount Web::App, at: '/'
model do
##
# Database adapter
#
# Available options:
#
# * SQL adapter
# adapter :sql, 'sqlite://db/#{project}_development.sqlite3'
# adapter :sql, 'postgresql://localhost/#{project}_development'
# adapter :sql, 'mysql://localhost/#{project}_development'
#
adapter :sql, ENV.fetch('DATABASE_URL')
##
# Migrations
#
migrations 'db/migrations'
schema 'db/schema.sql'
end
mailer do
root 'lib/#{project}/mailers'
# See https://guides.hanamirb.org/mailers/delivery
delivery :test
end
environment :development do
# See: https://guides.hanamirb.org/projects/logging
logger level: :debug
end
environment :production do
logger level: :info, formatter: :json, filter: []
mailer do
delivery :smtp, address: ENV.fetch('SMTP_HOST'), port: ENV.fetch('SMTP_PORT')
end
end
end
END
project_module = Hanami::Utils::String.new(project).classify
#
# lib/<project>.rb
#
expect("lib/#{project}.rb").to have_file_content <<~END
module #{project_module}
end
END
#
# public/.gitkeep
#
expect("public/.gitkeep").to be_an_existing_file
#
# config/initializers/.gitkeep
#
expect("config/initializers/.gitkeep").to be_an_existing_file
#
# lib/<project>/entities/.gitkeep
#
expect("lib/#{project}/entities/.gitkeep").to be_an_existing_file
#
# lib/<project>/mailers/.gitkeep
#
expect("lib/#{project}/mailers/.gitkeep").to be_an_existing_file
#
# lib/<project>/mailers/templates/.gitkeep
#
expect("lib/#{project}/mailers/templates/.gitkeep").to be_an_existing_file
#
# spec/<project>/entities/.gitkeep
#
expect("spec/#{project}/entities/.gitkeep").to be_an_existing_file
#
# spec/<project>/repositories/.gitkeep
#
expect("spec/#{project}/repositories/.gitkeep").to be_an_existing_file
#
# spec/<project>/mailers/.gitkeep
#
expect("spec/#{project}/mailers/.gitkeep").to be_an_existing_file
#
# spec/support/.gitkeep
#
expect("spec/support/.gitkeep").to be_an_existing_file
#
# Rakefile
#
expect("Rakefile").to have_file_content <<~END
require 'rake'
require 'hanami/rake_tasks'
begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
rescue LoadError
end
END
#
# spec/spec_helper.rb
#
expect("spec/spec_helper.rb").to have_file_content <<~END
# Require this file for unit tests
ENV['HANAMI_ENV'] ||= 'test'
require_relative '../config/environment'
Hanami.boot
Hanami::Utils.require!("\#{__dir__}/support")
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
#
# The `.rspec` file also contains a few flags that are not defaults but that
# users commonly want.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.run_all_when_everything_filtered = true
# Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. We recommend
# you configure your source control system to ignore this file.
config.example_status_persistence_file_path = "spec/examples.txt"
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in many cases may
# be too noisy due to issues in dependencies.
config.warnings = false
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = 'doc'
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
=end
end
END
#
# spec/features_helper.rb
#
expect("spec/features_helper.rb").to have_file_content <<~END
# Require this file for feature tests
require_relative './spec_helper'
require 'capybara'
require 'capybara/rspec'
RSpec.configure do |config|
config.include RSpec::FeatureExampleGroup
config.include Capybara::DSL, feature: true
config.include Capybara::RSpecMatchers, feature: true
end
END
#
# .gitignore
#
expect(".gitignore").to have_file_content <<-END
/db/*.sqlite
/public/assets*
/tmp
.env.local
.env.*.local
END
#
# apps/web/app.rb
#
expect("apps/web/app.rb").to have_file_content <<-END
require 'hanami/helpers'
require 'hanami/assets'
module Web
class App < Hanami::App
configure do
##
# BASIC
#
# Define the root path of this app.
# All paths specified in this configuration are relative to path below.
#
root __dir__
# Relative load paths where this app will recursively load the
# code.
#
# When you add new directories, remember to add them here.
#
load_paths << [
'controllers',
'views'
]
# Handle exceptions with HTTP statuses (true) or don't catch them (false).
# Defaults to true.
# See: http://www.rubydoc.info/gems/hanami-controller/#Exceptions_management
#
# handle_exceptions true
##
# HTTP
#
# Routes definitions for this app
# See: http://www.rubydoc.info/gems/hanami-router#Usage
#
routes 'config/routes'
# URI scheme used by the routing system to generate absolute URLs
# Defaults to "http"
#
# scheme 'https'
# URI host used by the routing system to generate absolute URLs
# Defaults to "localhost"
#
# host 'example.org'
# URI port used by the routing system to generate absolute URLs
# Argument: An object coercible to integer, defaults to 80 if the scheme
# is http and 443 if it's https
#
# This should only be configured if app listens to non-standard ports
#
# port 443
# Enable cookies
# Argument: boolean to toggle the feature
# A Hash with options
#
# Options:
# :domain - The domain (String - nil by default, not required)
# :path - Restrict cookies to a relative URI
# (String - nil by default)
# :max_age - Cookies expiration expressed in seconds
# (Integer - nil by default)
# :secure - Restrict cookies to secure connections
# (Boolean - Automatically true when using HTTPS)
# See #scheme and #ssl?
# :httponly - Prevent JavaScript access (Boolean - true by default)
#
# cookies true
# or
# cookies max_age: 300
# Enable sessions
# Argument: Symbol the Rack session adapter
# A Hash with options
#
# See: http://www.rubydoc.info/gems/rack/Rack/Session/Cookie
#
# sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']
# Configure Rack middleware for this app
#
# middleware.use Rack::Protection
# Default format for the requests that don't specify an HTTP_ACCEPT header
# Argument: A symbol representation of a mime type, defaults to :html
#
# default_request_format :html
# Default format for responses that don't consider the request format
# Argument: A symbol representation of a mime type, defaults to :html
#
# default_response_format :html
##
# TEMPLATES
#
# The layout to be used by all views
#
layout :app # It will load Web::Views::AppLayout
# The relative path to templates
#
templates 'templates'
##
# ASSETS
#
assets do
# JavaScript compressor
#
# Supported engines:
#
# * :builtin
# * :uglifier
# * :yui
# * :closure
#
# See: https://guides.hanamirb.org/assets/compressors
#
# In order to skip JavaScript compression comment the following line
javascript_compressor :builtin
# Stylesheet compressor
#
# Supported engines:
#
# * :builtin
# * :yui
# * :sass
#
# See: https://guides.hanamirb.org/assets/compressors
#
# In order to skip stylesheet compression comment the following line
stylesheet_compressor :builtin
# Specify sources for assets
#
sources << [
'assets'
]
end
##
# SECURITY
#
# X-Frame-Options is a HTTP header supported by modern browsers.
# It determines if a web page can or cannot be included via <frame> and
# <iframe> tags by untrusted domains.
#
# Web apps can send this header to prevent Clickjacking attacks.
#
# Read more at:
#
# * https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options
# * https://www.owasp.org/index.php/Clickjacking
#
security.x_frame_options 'DENY'
# X-Content-Type-Options prevents browsers from interpreting files as
# something else than declared by the content type in the HTTP headers.
#
# Read more at:
#
# * https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#X-Content-Type-Options
# * https://msdn.microsoft.com/en-us/library/gg622941%28v=vs.85%29.aspx
# * https://blogs.msdn.microsoft.com/ie/2008/09/02/ie8-security-part-vi-beta-2-update
#
security.x_content_type_options 'nosniff'
# X-XSS-Protection is a HTTP header to determine the behavior of the
# browser in case an XSS attack is detected.
#
# Read more at:
#
# * https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
# * https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#X-XSS-Protection
#
security.x_xss_protection '1; mode=block'
# Content-Security-Policy (CSP) is a HTTP header supported by modern
# browsers. It determines trusted sources of execution for dynamic
# contents (JavaScript) or other web related assets: stylesheets, images,
# fonts, plugins, etc.
#
# Web apps can send this header to mitigate Cross Site Scripting
# (XSS) attacks.
#
# The default value allows images, scripts, AJAX, fonts and CSS from the
# same origin, and does not allow any other resources to load (eg object,
# frame, media, etc).
#
# Inline JavaScript is NOT allowed. To enable it, please use:
# "script-src 'unsafe-inline'".
#
# Content Security Policy introduction:
#
# * http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# * https://www.owasp.org/index.php/Content_Security_Policy
# * https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29
#
# Inline and eval JavaScript risks:
#
# * http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
# * http://www.html5rocks.com/en/tutorials/security/content-security-policy/#eval-too
#
# Content Security Policy usage:
#
# * http://content-security-policy.com/
# * https://developer.mozilla.org/en-US/docs/Web/Security/CSP/Using_Content_Security_Policy
#
# Content Security Policy references:
#
# * https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
#
security.content_security_policy %{
form-action 'self';
frame-ancestors 'self';
base-uri 'self';
default-src 'none';
script-src 'self';
connect-src 'self';
img-src 'self' https: data:;
style-src 'self' 'unsafe-inline' https:;
font-src 'self';
object-src 'none';
plugin-types app/pdf;
child-src 'self';
frame-src 'self';
media-src 'self'
}
##
# FRAMEWORKS
#
# Configure the code that will yield each time Web::Action is included
# This is useful for sharing common functionality
#
# See: http://www.rubydoc.info/gems/hanami-controller#Configuration
controller.prepare do
# include MyAuthentication # included in all the actions
# before :authenticate! # run an authentication before callback
end
# Configure the code that will yield each time Web::View is included
# This is useful for sharing common functionality
#
# See: http://www.rubydoc.info/gems/hanami-view#Configuration
view.prepare do
include Hanami::Helpers
include Web::Assets::Helpers
end
end
##
# DEVELOPMENT
#
configure :development do
# Don't handle exceptions, render the stack trace
handle_exceptions false
end
##
# TEST
#
configure :test do
# Don't handle exceptions, render the stack trace
handle_exceptions false
end
##
# PRODUCTION
#
configure :production do
# scheme 'https'
# host 'example.org'
# port 443
assets do
# Don't compile static assets in production mode (eg. Sass, ES6)
#
# See: http://www.rubydoc.info/gems/hanami-assets#Configuration
compile false
# Use fingerprint file name for asset paths
#
# See: https://guides.hanamirb.org/assets/overview
fingerprint true
# Content Delivery Network (CDN)
#
# See: https://guides.hanamirb.org/assets/content-delivery-network
#
# scheme 'https'
# host 'cdn.example.org'
# port 443
# Subresource Integrity
#
# See: https://guides.hanamirb.org/assets/content-delivery-network/#subresource-integrity
subresource_integrity :sha256
end
end
end
end
END
#
# apps/web/config/routes.rb
#
expect("apps/web/config/routes.rb").to have_file_content <<-END
# Configure your routes here
# See: https://guides.hanamirb.org/routing/overview
#
# Example:
# get '/hello', to: ->(env) { [200, {}, ['Hello from Hanami!']] }
END
#
# apps/web/views/app_layout.rb
#
expect("apps/web/views/app_layout.rb").to have_file_content <<~END
module Web
module Views
class AppLayout
include Web::Layout
end
end
end
END
#
# apps/web/templates/app.html.erb
#
expect("apps/web/templates/app.html.erb").to have_file_content <<~END
<!DOCTYPE html>
<html>
<head>
<title>Web</title>
<%= favicon %>
</head>
<body>
<%= yield %>
</body>
</html>
END
#
# apps/web/assets/favicon.ico
#
expect("apps/web/assets/favicon.ico").to be_an_existing_file
#
# apps/web/controllers/.gitkeep
#
expect("apps/web/controllers/.gitkeep").to be_an_existing_file
#
# apps/web/assets/images/.gitkeep
#
expect("apps/web/assets/images/.gitkeep").to be_an_existing_file
#
# apps/web/assets/javascripts/.gitkeep
#
expect("apps/web/assets/javascripts/.gitkeep").to be_an_existing_file
#
# apps/web/assets/stylesheets/.gitkeep
#
expect("apps/web/assets/stylesheets/.gitkeep").to be_an_existing_file
#
# spec/web/features/.gitkeep
#
expect("spec/web/features/.gitkeep").to be_an_existing_file
#
# spec/web/controllers/.gitkeep
#
expect("spec/web/controllers/.gitkeep").to be_an_existing_file
end
end
context "with underscored project name" do
it_behaves_like "a new project" do
let(:input) { "cool_name" }
end
end
context "with dashed project name" do
it_behaves_like "a new project" do
let(:input) { "awesome-project" }
end
end
context "with camel case project name" do
it_behaves_like "a new project" do
let(:input) { "CaMElCaSE" }
end
end
context "with dot as project name" do
before do
root.mkpath
end
let(:root) { Pathname.new(Dir.pwd).join("tmp", "aruba", dir) }
let(:project) { "terrific_product" }
let(:dir) { "terrific product" }
it "generates project" do
cd(dir) do
run_cmd "hanami new ."
end
[
"create lib/#{project}.rb",
"create lib/#{project}/entities/.gitkeep",
"create lib/#{project}/repositories/.gitkeep",
"create lib/#{project}/mailers/.gitkeep",
"create lib/#{project}/mailers/templates/.gitkeep",
"create spec/#{project}/entities/.gitkeep",
"create spec/#{project}/repositories/.gitkeep",
"create spec/#{project}/mailers/.gitkeep"
].each do |output|
expect(all_output).to match(/#{output}/)
end
within_project_directory(dir) do
#
# .hanamirc
#
expect(".hanamirc").to have_file_content %r{project=#{project}}
end
end
end
context "with missing name" do
it "fails" do
output = <<~OUT
ERROR: "hanami new" was called with no arguments
Usage: "hanami new PROJECT"
OUT
run_cmd "hanami new", output, exit_status: 1
end
end
it 'prints help message' do
output = <<-OUT
Command:
hanami new
Usage:
hanami new PROJECT
Description:
Generate a new Hanami project
Arguments:
PROJECT # REQUIRED The project name
Options:
--database=VALUE, -d VALUE # Database (mysql/mysql2/postgresql/postgres/sqlite/sqlite3), default: "sqlite"
--app-name=VALUE # App name, default: "web"
--app-base-url=VALUE # App base URL, default: "/"
--template=VALUE # Template engine (erb/haml/slim), default: "erb"
--test=VALUE # Project testing framework (rspec/minitest), default: "rspec"
--[no-]hanami-head # Use Hanami HEAD (true/false), default: false
--help, -h # Print this help
Examples:
hanami new bookshelf # Basic usage
hanami new bookshelf --test=rspec # Setup RSpec testing framework
hanami new bookshelf --database=postgres # Setup Postgres database
hanami new bookshelf --template=slim # Setup Slim template engine
hanami new bookshelf --hanami-head # Use Hanami HEAD
OUT
run_cmd 'hanami new --help', output
end
private
def default_git_branch
result = `git config --list`.scan(/init\.defaultBranch\=[[:alpha:]]*/i).first
branch = if result.nil?
"(main|master)"
else
result.split("=").last
end
/On branch #{branch}/
end
end

View File

@ -1,39 +0,0 @@
# frozen_string_literal: true
require "pathname"
RSpec.describe "CLI plugins", type: :integration do
xit "includes its commands in CLI output" do
with_project do
bundle_exec "hanami"
expect(out).to include("hanami plugin [SUBCOMMAND]")
end
end
xit "executes command from plugin" do
with_project do
bundle_exec "hanami plugin version"
expect(out).to include("v0.1.0")
end
end
# See https://github.com/hanami/hanami/issues/838
it "guarantees 'hanami new' to generate a project" do
project = "bookshelf_without_gemfile"
with_system_tmp_directory do
run_cmd_with_clean_env "hanami new #{project}"
destination = Pathname.new(Dir.pwd).join(project)
expect(destination).to exist
end
end
private
def with_project
super("bookshelf", gems: { "hanami-plugin" => { groups: [:plugins], path: Pathname.new(__dir__).join("..", "..", "..", "spec", "support", "fixtures", "hanami-plugin").realpath.to_s } }) do
yield
end
end
end

View File

@ -1,49 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami routes", type: :integration do
it "prints app routes" do
with_project do
generate "app admin"
write "lib/ping.rb", <<~EOF
class Ping
def call(env)
[200, {}, ["PONG"]]
end
end
EOF
unshift "config/environment.rb", "require_relative '../lib/ping'"
replace "config/environment.rb", "Hanami.configure do", "Hanami.configure do\nmount Ping, at: '/ping'"
generate "action web home#index --url=/"
generate "action web books#create --url=/books --method=POST"
generate "action admin home#index --url=/"
hanami "routes"
expect(out).to eq "Name Method Path Action \n\n /ping Ping \n\n Name Method Path Action \n\n GET, HEAD /admin Admin::Controllers::Home::Index\n\n Name Method Path Action \n\n GET, HEAD / Web::Controllers::Home::Index \n POST /books Web::Controllers::Books::Create"
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami routes
Usage:
hanami routes
Description:
Prints routes
Options:
--help, -h # Print this help
OUT
run_cmd 'hanami routes --help', output
end
end
end

View File

@ -1,626 +0,0 @@
# frozen_string_literal: true
require "json"
RSpec.describe "hanami server", type: :integration do
context "without routes" do
it "shows welcome page" do
with_project do
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
expect(page).to have_content("The web, with simplicity.")
expect(page).to have_content("Hanami is Open Source Software for MVC web development with Ruby.")
expect(page).to have_content("bundle exec hanami generate action web 'home#index' --url=/")
end
end
end
it "shows welcome page for generated app" do
with_project do
generate "app admin"
server do
visit "/admin"
expect(page).to have_content("bundle exec hanami generate action admin 'home#index' --url=/")
end
end
end
end
context "with routes" do
it "serves action" do
with_project do
server do
generate "action web home#index --url=/"
visit "/"
expect(page).to have_title("Web")
end
end
end
it "serves static asset" do
with_project do
server do
write "apps/web/assets/javascripts/app.js", <<~EOF
console.log('test');
EOF
visit "/assets/app.js"
expect(page).to have_content("console.log('test');")
end
end
end
it "serves contents from database" do
with_project do
setup_model
console do |input, _, _|
input.puts("BookRepository.new.create(title: 'Learn Hanami')")
input.puts("exit")
end
generate "action web books#show --url=/books/:id"
rewrite "apps/web/controllers/books/show.rb", <<~EOF
module Web::Controllers::Books
class Show
include Web::Action
expose :book
def call(params)
@book = BookRepository.new.find(params[:id]) or halt(404)
end
end
end
EOF
rewrite "apps/web/templates/books/show.html.erb", <<~EOF
<h1><%= book.title %></h1>
EOF
server do
visit "/books/1"
expect(page).to have_content("Learn Hanami")
end
end
end
end
context "logging" do
let(:log) { "log/development.log" }
let(:project) { "bookshelf" }
context "when enabled" do
it "logs GET requests" do
with_project(project) do
touch log
replace "config/environment.rb", "logger level: :debug", %(logger level: :debug, stream: "#{log}")
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
end
content = contents(log)
expect(content).to include("[#{project}] [INFO]")
expect(content).to match(%r{HTTP/1.1 GET 200 (.*) /})
end
end
it "logs GET requests with query string" do
with_project(project) do
touch log
replace "config/environment.rb", "logger level: :debug", %(logger level: :debug, stream: "#{log}")
run_cmd "hanami generate action web home#index --url=/", []
server do
visit "/?ping=pong"
expect(page).to have_title("Web")
end
content = contents(log)
expect(content).to include("[#{project}] [INFO]")
expect(content).to match(%({"ping"=>"pong"}))
end
end
it "logs non-GET requests with payload" do
with_project(project) do
touch log
replace "config/environment.rb", "logger level: :debug", %(logger level: :debug, stream: "#{log}")
run_cmd "hanami generate action web books#create --method=POST", []
server do
post "/books", book: { title: "Functions" }
end
content = contents(log)
expect(content).to include("[#{project}] [INFO]")
expect(content).to match(%({"book"=>{"title"=>"Functions"}}))
end
end
it "logs non-GET requests from body parsers" do
with_project(project) do
touch log
replace "config/environment.rb", "logger level: :debug", %(logger level: :debug, stream: "#{log}")
inject_line_after "config/environment.rb", "Hanami.configure", "require 'hanami/middleware/body_parser'\nmiddleware.use Hanami::Middleware::BodyParser, :json"
run_cmd "hanami generate action web books#create --method=POST", []
inject_line_after "apps/web/controllers/books/create.rb", "call", 'Hanami.logger.debug(request.env["CONTENT_TYPE"]);self.body = %({"status":"OK"})'
server do
post "/books", book: { title: "Why we sleep" }
post "/books", JSON.generate(book: { title: "Parsers" }), "CONTENT_TYPE" => "app/json", "HTTP_ACCEPT" => "app/json"
post "/books", JSON.generate(%w[this is cool]), "CONTENT_TYPE" => "app/json", "HTTP_ACCEPT" => "app/json"
end
content = contents(log)
expect(content).to include("[#{project}] [INFO]")
expect(content).to include("POST 200")
expect(content).to include("app/x-www-form-urlencoded")
expect(content).to include(%({"book"=>{"title"=>"Why we sleep"}}))
expect(content).to include("app/json")
expect(content).to include(%({"book"=>{"title"=>"Parsers"}}))
expect(content).to include(%({"_"=>["this", "is", "cool"]}))
end
end
end
context "when not enabled" do
it "does not log request" do
with_project(project) do
replace "config/environment.rb", "logger level: :debug", ""
server do
visit "/"
end
expect(log).to_not be_an_existing_file
end
end
end
end
context "--host" do
it "starts on given host" do
with_project do
server(host: "127.0.0.1") do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
end
end
end
xit "fails when missing" do
with_project do
server(host: nil)
expect(exitstatus).to eq(1)
end
end
end
context "--port" do
it "starts on given port" do
with_project do
server(port: 1982) do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
end
end
end
xit "fails when missing" do
with_project do
server(port: nil)
expect(exitstatus).to eq(1)
end
end
end
context "environment" do
it "starts with given environment" do
with_project do
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = Hanami.env
end
end
end
EOF
RSpec::Support::Env["HANAMI_ENV"] = env = "production"
RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
RSpec::Support::Env["SMTP_HOST"] = "localhost"
RSpec::Support::Env["SMTP_PORT"] = "25"
server do
visit "/"
expect(page).to have_content(env)
end
end
end
xit "fails when missing" do
with_project do
server(environment: nil)
expect(exitstatus).to eq(1)
end
end
end
context "puma" do
it "starts" do
with_project("bookshelf_server_puma", server: :puma) do
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
end
end
end
end
context "unicorn" do
it "starts" do
with_project("bookshelf_server_unicorn", server: :unicorn) do
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
end
end
end
end
context "code reloading" do
it "reloads templates code" do
with_project do
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
generate "action web home#index --url=/"
rewrite "apps/web/templates/home/index.html.erb", <<~EOF
<h1>Hello, World!</h1>
EOF
visit "/"
expect(page).to have_title("Web")
expect(page).to have_content("Hello, World!")
end
end
end
it "reloads view" do
with_project do
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
generate "action web home#index --url=/"
rewrite "apps/web/views/home/index.rb", <<~EOF
module Web::Views::Home
class Index
include Web::View
def greeting
"Ciao!"
end
end
end
EOF
rewrite "apps/web/templates/home/index.html.erb", <<~EOF
<%= greeting %>
EOF
visit "/"
expect(page).to have_title("Web")
expect(page).to have_content("Ciao!")
end
end
end
it "reloads action" do
with_project do
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = "Hi!"
end
end
end
EOF
visit "/"
expect(page).to have_content("Hi!")
end
end
end
it "reloads model" do
project_name = "bookshelf"
with_project(project_name) do
# STEP 1: prepare the database and the repository
generate_model "user"
generate_migration "create_users", <<~EOF
Hanami::Model.migration do
change do
create_table :users do
primary_key :id
column :name, String
end
execute "INSERT INTO users (name) VALUES('L')"
execute "INSERT INTO users (name) VALUES('MG')"
end
end
EOF
rewrite "lib/#{project_name}/repositories/user_repository.rb", <<~EOF
class UserRepository < Hanami::Repository
def listing
all
end
end
EOF
hanami "db prepare"
# STEP 2: generate the action
generate "action web users#index --url=/users"
rewrite "apps/web/controllers/users/index.rb", <<~EOF
module Web::Controllers::Users
class Index
include Web::Action
def call(params)
self.body = UserRepository.new.listing.map(&:name).join(", ")
end
end
end
EOF
server do
# STEP 3: visit the page
visit "/users"
expect(page).to have_content("L, MG")
# STEP 4: change the repository, then visit the page again
rewrite "lib/#{project_name}/repositories/user_repository.rb", <<~EOF
class UserRepository < Hanami::Repository
def listing
all.reverse
end
end
EOF
visit "/users"
expect(page).to have_content("MG, L")
end
end
end
xit "reloads asset" do
with_project do
server do
write "apps/web/assets/stylesheets/style.css", <<~EOF
body { background-color: #fff; }
EOF
visit "/assets/style.css"
expect(page).to have_content("#fff")
rewrite "apps/web/assets/stylesheets/style.css", <<~EOF
body { background-color: #333; }
EOF
visit "/assets/style.css"
expect(page).to have_content("#333")
end
end
end
end
context "without code reloading" do
it "doesn't reload code" do
with_project do
server("no-code-reloading" => nil) do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
generate "action web home#index --url=/"
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
end
end
end
end
context "without hanami model" do
it "uses custom model domain" do
project_without_hanami_model("bookshelf", gems: ["dry-struct"]) do
write "lib/entities/access_token.rb", <<~EOF
require 'dry-struct'
require 'securerandom'
module Types
include Dry::Types.module
end
class AccessToken < Dry::Struct
attribute :id, Types::String.default { SecureRandom.uuid }
attribute :secret, Types::String
attribute :digest, Types::String
end
EOF
generate "action web access_tokens#show --url=/access_tokens/:id"
rewrite "apps/web/controllers/access_tokens/show.rb", <<~EOF
module Web::Controllers::AccessTokens
class Show
include Web::Action
expose :access_token
def call(params)
@access_token = AccessToken.new(id: '1', secret: 'shh', digest: 'abc')
end
end
end
EOF
rewrite "apps/web/templates/access_tokens/show.html.erb", <<~EOF
<h1><%= access_token.secret %></h1>
EOF
server do
visit "/access_tokens/1"
visit "/access_tokens/1" # forces code reloading
expect(page).to have_content("shh")
end
end
end
end
context "without mailer" do
it "returns page" do
with_project do
remove_block "config/environment.rb", "mailer do"
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
end
end
end
end
it "prints help message" do
with_project do
output = <<-OUT
Command:
hanami server
Usage:
hanami server
Description:
Start Hanami server (only for development)
Options:
--server=VALUE # Force a server engine (eg, webrick, puma, thin, etc..)
--host=VALUE # The host address to bind to
--port=VALUE, -p VALUE # The port to run the server on
--debug=VALUE # Turn on debug output
--warn=VALUE # Turn on warnings
--daemonize=VALUE # Daemonize the server
--pid=VALUE # Path to write a pid file after daemonize
--[no-]code-reloading # Code reloading, default: true
--help, -h # Print this help
Examples:
hanami server # Basic usage (it uses the bundled server engine)
hanami server --server=webrick # Force `webrick` server engine
hanami server --host=0.0.0.0 # Bind to a host
hanami server --port=2306 # Bind to a port
hanami server --no-code-reloading # Disable code reloading
OUT
run_cmd 'hanami server --help', output
end
end
context "with HANAMI_APPS ENV variable" do
it "loads only specific app" do
with_project do
generate "app admin"
remove_line "config/environment.rb", "require_relative '../apps/admin/app'"
remove_line "config/environment.rb", "mount Admin::App"
remove_line "config/environment.rb", "require_relative '../apps/web/app'"
remove_line "config/environment.rb", "mount Web::App"
inject_line_after "config/environment.rb", "Hanami.configure", <<-EOL
if Hanami.app?(:admin)
require_relative '../apps/admin/app'
mount Admin::App, at: '/admin'
end
if Hanami.app?(:web)
require_relative '../apps/web/app'
mount Web::App, at: '/'
end
EOL
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = "app: web"
end
end
end
EOF
generate "action admin home#index --url=/"
rewrite "apps/admin/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Admin::Action
def call(params)
self.body = "app: admin"
end
end
end
EOF
RSpec::Support::Env["HANAMI_APPS"] = "web"
server do
visit "/"
expect(page).to have_content("app: web")
visit "/admin"
expect(page).to have_content("Not Found")
end
end
end
end
end

View File

@ -1,85 +0,0 @@
# frozen_string_literal: true
RSpec.describe "hanami version", type: :integration do
context "within a project" do
it "prints current version" do
with_project do
run_cmd 'hanami version', "v#{Hanami::VERSION}"
end
end
it "prints current version with v alias" do
with_project do
run_cmd 'hanami v', "v#{Hanami::VERSION}"
end
end
it "prints current version with -v alias" do
with_project do
run_cmd 'hanami -v', "v#{Hanami::VERSION}"
end
end
it "prints current version with --version alias" do
with_project do
run_cmd 'hanami --version', "v#{Hanami::VERSION}"
end
end
it "prints help message" do
with_project do
output = <<~OUT
Command:
hanami version
Usage:
hanami version
Description:
Print Hanami version
Options:
--help, -h # Print this help
OUT
run_cmd 'hanami version --help', output
end
end
end
context "outside of a project" do
it 'prints current version' do
run_cmd 'hanami version', "v#{Hanami::VERSION}"
end
it 'prints current version with v alias' do
run_cmd 'hanami v', "v#{Hanami::VERSION}"
end
it 'prints current version with -v alias' do
run_cmd 'hanami -v', "v#{Hanami::VERSION}"
end
it 'prints current version with --version alias' do
run_cmd 'hanami --version', "v#{Hanami::VERSION}"
end
it "prints help message" do
output = <<~OUT
Command:
hanami version
Usage:
hanami version
Description:
Print Hanami version
Options:
--help, -h # Print this help
OUT
run_cmd 'hanami version --help', output
end
end
end

View File

@ -1,35 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Early Hints", type: :integration do
it "pushes assets" do
skip "curl not installed" if which("curl").nil?
with_project("bookshelf", server: :puma) do
generate "action web home#index --url=/"
write "apps/web/assets/javascripts/app.css", <<~EOF
body { margin: 0; }
EOF
inject_line_after "apps/web/templates/app.html.erb", /favicon/, %(<%= stylesheet "app" %>)
inject_line_after "config/environment.rb", /mount/, "early_hints true"
write "config/puma.rb", <<~EOF
early_hints true
EOF
port = RSpec::Support::RandomPort.call
server(port: port) do
sleep 2
# The Ruby HTTP client that we use for testing (excon), fails to connect to the server.
# It's very likely that it assumes that for each request, the server will return only one response.
# But in case of Early Hints (103) the returned response are multiple: `n` Early Hints (103) + OK (200).
#
# For this reason we fall back to cURL for this test.
system_exec("curl -i -v http://localhost:#{port}/")
expect(out).to include("Link: </assets/app.css>; rel=preload; as=style")
end
end
end
end

View File

@ -1,244 +0,0 @@
# frozen_string_literal: true
RSpec.describe "handle exceptions", type: :integration do
it "doesn't handle exceptions in development mode" do
with_project do
generate_action
server do
get "/books/1"
expect(last_response.status).to eq(500)
end
end
end
it "doesn't handle exceptions in test mode" do
with_project do
generate_action
RSpec::Support::Env["HANAMI_ENV"] = "test"
server do
get "/books/1"
expect(last_response.status).to eq(500)
end
end
end
context "when handles exceptions in production mode" do
it "it returns the expected status" do
with_project do
generate_action
setup_production_env
server do
get "/books/1"
expect(last_response.status).to eq(400)
end
end
end
context "and an exception is raised from a template" do
it "it returns a 500 and it renders a custom template if it exists" do
with_project do
generate "action web books#show --url=/books/:id"
rewrite "apps/web/templates/books/show.html.erb", <<~EOF
<%= raise ArgumentError.new("oh nooooo") %>
EOF
write "apps/web/templates/500.html.erb", <<~EOF
This is a custom template for 500 error
EOF
setup_production_env
server do
get "/books/1"
expect(last_response.status).to eq(500)
expect(last_response.body).to eq("This is a custom template for 500 error\n")
end
end
end
it "it returns a 500 and it renders the default template if custom template doesn't exist" do
with_project do
generate "action web books#show --url=/books/:id"
rewrite "apps/web/templates/books/show.html.erb", <<~EOF
<%= raise ArgumentError.new("oh nooooo") %>
EOF
setup_production_env
server do
get "/books/1"
expect(last_response.status).to eq(500)
expect(last_response.body).to include("<h2>500 - Internal Server Error</h2>")
end
end
end
it "it returns a 500 and renders backtrace error if an exception is raised from 500 custom template" do
with_project do
generate "action web books#show --url=/books/:id"
rewrite "apps/web/templates/books/show.html.erb", <<~EOF
<%= raise ArgumentError.new("oh nooooo") %>
EOF
write "apps/web/templates/500.html.erb", <<~EOF
<%= raise ArgumentError.new("Error from custom template") %>
This is a custom template for 500 error
EOF
setup_production_env
server do
get "/books/1"
expect(last_response.status).to eq(500)
expect(last_response.body).to_not include("This is a custom template for 500 error")
expect(last_response.body).to include("Error from custom template")
end
end
end
end
context "and an exception is raised from a view" do
it "it returns a 500 and it renders a custom template if it exists" do
with_project do
generate "action web books#show --url=/books/:id"
rewrite "apps/web/views/books/show.rb", <<~EOF
module Web::Views::Books
class Show
include Web::View
def header
raise ArgumentError.new("oh nooooo")
end
end
end
EOF
rewrite "apps/web/templates/books/show.html.erb", <<~EOF
<%= header %>
EOF
write "apps/web/templates/500.html.erb", <<~EOF
This is a custom template for 500 error
EOF
setup_production_env
server do
get "/books/1"
expect(last_response.status).to eq(500)
expect(last_response.body).to eq("This is a custom template for 500 error\n")
end
end
end
it "it returns a 500 and it renders the default template if custom template doesn't exist" do
with_project do
generate "action web books#show --url=/books/:id"
rewrite "apps/web/views/books/show.rb", <<~EOF
module Web::Views::Books
class Show
include Web::View
def header
raise ArgumentError.new("oh nooooo")
end
end
end
EOF
rewrite "apps/web/templates/books/show.html.erb", <<~EOF
<%= header %>
EOF
setup_production_env
server do
get "/books/1"
expect(last_response.status).to eq(500)
expect(last_response.body).to include("<h2>500 - Internal Server Error</h2>")
end
end
end
it "it returns a 500 and renders backtrace error if an exception is raised from 500 custom template" do
with_project do
generate "action web books#show --url=/books/:id"
rewrite "apps/web/views/books/show.rb", <<~EOF
module Web::Views::Books
class Show
include Web::View
def header
raise ArgumentError.new("oh nooooo")
end
end
end
EOF
rewrite "apps/web/templates/books/show.html.erb", <<~EOF
<%= header %>
EOF
write "apps/web/templates/500.html.erb", <<~EOF
<%= raise ArgumentError.new("Error from custom template") %>
This is a custom template for 500 error
EOF
setup_production_env
server do
get "/books/1"
expect(last_response.status).to eq(500)
expect(last_response.body).to_not include("This is a custom template for 500 error")
expect(last_response.body).to include("Error from custom template")
end
end
end
end
end
private
def generate_action
generate "action web books#show --url=/books/:id"
rewrite "apps/web/controllers/books/show.rb", <<~EOF
module Web::Controllers::Books
class Show
include Web::Action
handle_exception ArgumentError => 400
def call(params)
raise ArgumentError.new("oh nooooo")
end
end
end
EOF
end
def setup_production_env
RSpec::Support::Env["HANAMI_ENV"] = "production"
RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
RSpec::Support::Env["SMTP_HOST"] = "localhost"
RSpec::Support::Env["SMTP_PORT"] = "25"
end
end

View File

@ -1,89 +0,0 @@
# frozen_string_literal: true
RSpec.describe "HTTP HEAD", type: :integration do
it "returns empty body for HEAD requests" do
with_project do
generate "action web home#index --url=/"
server do
head "/"
expect(last_response.status).to eq(200)
expect(last_response.body).to eq("")
end
end
end
it "returns empty body for HEAD requests when body is set by the action" do
with_project do
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = "Hello"
end
end
end
EOF
server do
head "/"
expect(last_response.status).to eq(200)
expect(last_response.body).to eq("")
end
end
end
it "returns empty body for HEAD requests when body is set by the view" do
with_project do
generate "action web home#index --url=/"
rewrite "apps/web/views/home/index.rb", <<~EOF
module Web::Views::Home
class Index
include Web::View
def render
"World"
end
end
end
EOF
server do
head "/"
expect(last_response.status).to eq(200)
expect(last_response.body).to eq("")
end
end
end
it "returns empty body for HEAD requests with send file" do
with_project do
write "public/static.txt", "Plain text file"
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
send_file "static.txt"
end
end
end
EOF
server do
head "/"
expect(last_response.status).to eq(200)
expect(last_response.body).to eq("")
end
end
end
end

View File

@ -1,29 +0,0 @@
# frozen_string_literal: true
RSpec.describe "HTTP headers", type: :integration do
it "returns HTTP headers" do
with_project do
generate "action web home#index --url=/"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers.keys).to eq(
%w[
X-Frame-Options X-Content-Type-Options X-Xss-Protection
Content-Security-Policy Content-Type Content-Length
Server Date Connection
]
)
expect(last_response.headers["X-Frame-Options"]).to eq("DENY")
expect(last_response.headers["X-Content-Type-Options"]).to eq("nosniff")
expect(last_response.headers["X-Xss-Protection"]).to eq("1; mode=block")
expect(last_response.headers["Content-Security-Policy"]).to eq("form-action 'self'; frame-ancestors 'self'; base-uri 'self'; default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self' https: data:; style-src 'self' 'unsafe-inline' https:; font-src 'self'; object-src 'none'; plugin-types app/pdf; child-src 'self'; frame-src 'self'; media-src 'self'")
expect(last_response.headers["Content-Type"]).to eq("text/html; charset=utf-8")
expect(Integer(last_response.headers["Content-Length"])).to be > 0
end
end
end
end

View File

@ -1,32 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Mailer", type: :integration do
it "use a mailer" do
with_project do
generate "mailer welcome"
write "lib/bookshelf/mailers/default_user.rb", <<~EOF
module Mailers
module DefaultUser
def user_name
"Alfonso"
end
end
end
EOF
replace "config/environment.rb", "delivery :test", <<-EOF
delivery :test
prepare do
include Mailers::DefaultUser
end
EOF
console do |input, _, _|
input.puts("Mailers::Welcome.new.user_name")
end
expect(out).to include("Alfonso")
end
end
end

View File

@ -1,81 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Project middleware", type: :integration do
it "mounts Rack middleware" do
with_project do
generate_middleware
unshift "config/environment.rb", 'require "rack/etag"'
unshift "config/environment.rb", 'require_relative "./middleware/runtime"'
unshift "config/environment.rb", 'require_relative "./middleware/custom"'
inject_line_after "config/environment.rb", "Hanami.configure", <<~EOL
middleware.use Middleware::Runtime
middleware.use Middleware::Custom, "OK"
middleware.use Rack::ETag
EOL
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = "OK"
end
end
end
EOF
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["X-Runtime"]).to eq("1ms")
expect(last_response.headers["X-Custom"]).to eq("OK")
expect(last_response.headers["ETag"]).to_not be_nil
end
end
end
private
def generate_middleware # rubocop:disable Metrics/MethodLength
write "config/middleware/runtime.rb", <<~EOF
module Middleware
class Runtime
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers["X-Runtime"] = "1ms"
[status, headers, body]
end
end
end
EOF
write "config/middleware/custom.rb", <<~EOF
module Middleware
class Custom
def initialize(app, value)
@app = app
@value = value
end
def call(env)
status, headers, body = @app.call(env)
headers["X-Custom"] = @value
[status, headers, body]
end
end
end
EOF
end
end

View File

@ -1,88 +0,0 @@
require "resolv-replace"
require "net/http"
require "uri"
RSpec.describe "mount apps", type: :integration do
before do
stub_dns_hosts("127.0.0.1 #{host} www.#{host} #{subdomain} localhost")
end
let(:host) { "bookshelf.test" }
let(:subdomain) { "beta.#{host}" }
context "with apps mounted with path" do
it "shows welcome page" do
with_project do
generate_host_middleware
generate "app admin"
generate "app beta"
replace "config/environment.rb", "Beta::App", %( mount Beta::App, at: "/", host: "#{subdomain}")
server do
# Web
visit "/"
expect(page).to have_content("bundle exec hanami generate action web 'home#index' --url=/")
# Admin
visit "/admin"
expect(page).to have_content("bundle exec hanami generate action admin 'home#index' --url=/")
end
end
end
end
context "when apps mounted with host: option" do
it "shows welcome page" do
with_project do
generate_host_middleware
generate "app admin"
generate "app beta"
replace "config/environment.rb", "Beta::App", %( mount Beta::App, at: "/", host: "#{subdomain}")
port = RSpec::Support::RandomPort.call
server(port: port) do
# Beta
response = raw_http_request("http://#{subdomain}:#{port}")
expect(response.body).to include("bundle exec hanami generate action beta 'home#index' --url=/")
end
end
end
end
private
def generate_host_middleware
unshift "config/environment.rb", 'require_relative "./middleware/host"'
inject_line_after "config/environment.rb", "Hanami.configure", <<-EOL
middleware.use Middleware::Host
EOL
write "config/middleware/host.rb", <<-EOF
require "uri"
module Middleware
class Host
def initialize(app)
@app = app
end
def call(env)
host = URI.parse(env["REQUEST_URI"]).host
env["SERVER_NAME"] = host
env["HTTP_HOST"] = host
env["HTTP_X_FORWARDED_HOST"] = host
@app.call(env)
end
end
end
EOF
end
def raw_http_request(uri)
Net::HTTP.get_response(URI.parse(uri))
end
end

View File

@ -1,40 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Project initializers", type: :integration do
it "mounts Rack middleware" do
with_project("project_initializers", gems: ["i18n"]) do
write "config/locales/en.yml", <<~EOF
en:
greeting: "Welcome stranger"
EOF
write "config/initializers/i18n.rb", <<~EOF
require 'i18n'
I18n.load_path = Dir['config/locales/*.yml']
I18n.backend.load_translations
EOF
generate "action web home#index --url=/"
rewrite "apps/web/views/home/index.rb", <<~EOF
module Web::Views::Home
class Index
include Web::View
def greeting
I18n.t(:greeting)
end
end
end
EOF
rewrite "apps/web/templates/home/index.html.erb", <<~EOF
<h1><%= greeting%></h1>
EOF
server do
get "/"
expect(last_response.body).to include("Welcome stranger")
end
end
end
end

View File

@ -1,35 +0,0 @@
# frozen_string_literal: true
RSpec.describe "rackup", type: :integration do
it "serves contents from database" do
with_project do
setup_model
console do |input, _, _|
input.puts("BookRepository.new.create(title: 'Learn Hanami')")
input.puts("exit")
end
generate "action web books#show --url=/books/:id"
rewrite "apps/web/controllers/books/show.rb", <<~EOF
module Web::Controllers::Books
class Show
include Web::Action
expose :book
def call(params)
@book = BookRepository.new.find(params[:id]) or halt(404)
end
end
end
EOF
rewrite "apps/web/templates/books/show.html.erb", <<~EOF
<h1><%= book.title %></h1>
EOF
rackup do
visit "/books/1"
expect(page).to have_content("Learn Hanami")
end
end
end
end

View File

@ -1,67 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Rake: default task", type: :integration do
context "with Minitest" do
xit "runs tests" do
with_project("bookshelf", test: "minitest") do
setup_model
prepare_development_database
generate_development_data
prepare_test_database
write "spec/bookshelf/repositories/book_repository_spec.rb", <<~EOF
require 'spec_helper'
describe BookRepository do
before do
BookRepository.new.clear
end
it 'finds all the records' do
BookRepository.new.all.to_a.must_equal []
end
end
EOF
bundle_exec "rake"
expect(out).to include("2 runs, 3 assertions, 0 failures, 0 errors, 0 skips")
assert_development_data
end
end
end
private
def prepare_development_database
prepare_database
end
def prepare_test_database
prepare_database env: "test"
end
def generate_development_data
migrate
console do |input, _, _|
input.puts("BookRepository.new.create(title: 'Learn Hanami')")
input.puts("exit")
end
end
def assert_development_data
console do |input, _, _|
input.puts("BookRepository.new.all.to_a.count")
input.puts("exit")
end
expect(out).to include("\n1")
end
def prepare_database(env: nil)
hanami "db prepare", env: env
end
end

View File

@ -1,69 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Rake: default task", type: :integration do
context "with RSpec" do
it "runs tests" do
with_project("bookshelf", test: "rspec") do
setup_model
prepare_development_database
generate_development_data
prepare_test_database
generate "mailer bookshelf"
write "spec/bookshelf/repositories/book_repository_spec.rb", <<~EOF
RSpec.describe BookRepository do
before do
described_class.new.clear
end
it 'finds all the records' do
expect(described_class.new.all.to_a).to eq([])
end
end
EOF
bundle_exec "rake"
# The default mailer_spec fails on purpose so you set the correct delivery information.
expect(out).to include("3 examples, 1 failure")
assert_development_data
end
end
end
private
def prepare_development_database
prepare_database
end
def prepare_test_database
prepare_database env: "test"
end
def generate_development_data
migrate
console do |input, _, _|
input.puts("BookRepository.new.create(title: 'Learn Hanami')")
input.puts("exit")
end
end
def assert_development_data
console do |input, _, _|
input.puts("BookRepository.new.all.to_a.count")
input.puts("exit")
end
expect(out).to include("\n1")
end
def prepare_database(env: nil)
hanami "db prepare", env: env
end
end

View File

@ -1,61 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Routing helpers", type: :integration do
it "uses routing helpers within action" do
with_project do
generate "action web home#index --url=/"
generate "action web books#index --url=/books"
# Add `as:` option, so it can be used by the routing helper
replace "apps/web/config/routes.rb", "/books", "get '/books', to: 'books#index', as: :books"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
redirect_to routes.books_url
end
end
end
EOF
server do
visit "/"
expect(current_path).to eq("/books")
end
end
end
it "uses routing helpers within view" do
with_project do
generate "action web books#index --url=/books"
generate "action web books#show --url=/books/:id"
# Add `as:` option, so it can be used by the routing helper
replace "apps/web/config/routes.rb", "/books/:id", "get '/books/:id', to: 'books#show', as: :book"
rewrite "apps/web/views/books/index.rb", <<~EOF
module Web::Views::Books
class Index
include Web::View
def featured_book_path
routes.path(:book, id: 23)
end
end
end
EOF
rewrite "apps/web/templates/books/index.html.erb", <<~EOF
<h1>Books</h1>
<h2><a href="<%= featured_book_path %>">Featured Book</a></h2>
EOF
server do
visit "/books"
expect(page.body).to include(%(<a href="/books/23">Featured Book</a>))
end
end
end
end

View File

@ -1,46 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Content-Security-Policy header", type: :integration do
it "returns default value" do
with_project do
generate "action web home#index --url=/"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["Content-Security-Policy"]).to eq("form-action 'self'; frame-ancestors 'self'; base-uri 'self'; default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self' https: data:; style-src 'self' 'unsafe-inline' https:; font-src 'self'; object-src 'none'; plugin-types app/pdf; child-src 'self'; frame-src 'self'; media-src 'self'")
end
end
end
it "returns custom value" do
with_project do
generate "action web home#index --url=/"
replace "apps/web/app.rb", "script-src 'self';", "script-src 'self' https://code.jquery.com;"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["Content-Security-Policy"]).to eq("form-action 'self'; frame-ancestors 'self'; base-uri 'self'; default-src 'none'; script-src 'self' https://code.jquery.com; connect-src 'self'; img-src 'self' https: data:; style-src 'self' 'unsafe-inline' https:; font-src 'self'; object-src 'none'; plugin-types app/pdf; child-src 'self'; frame-src 'self'; media-src 'self'")
end
end
end
it "doesn't send header if setting is removed" do
with_project do
generate "action web home#index --url=/"
replace "apps/web/app.rb", "security.content_security_policy %{", "%{"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers).to_not have_key("Content-Security-Policy")
end
end
end
end

View File

@ -1,42 +0,0 @@
# frozen_string_literal: true
RSpec.describe "CSRF protection", type: :integration do
it "protects POST endpoints from invalid token" do
with_project do
generate "action web books#create --url=/books --method=POST"
replace "apps/web/app.rb", "# sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']", "sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']"
server do
post "/books", title: "TDD", _csrf_token: "invalid"
expect(last_response.status).to eq(500)
end
end
end
it "protects PATCH endpoints from invalid token" do
with_project do
generate "action web books#update --url=/books/:id --method=PATCH"
replace "apps/web/app.rb", "# sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']", "sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']"
server do
patch "/books/1", title: "Foo", _csrf_token: "invalid"
expect(last_response.status).to eq(500)
end
end
end
it "protects DELETE endpoints from invalid token" do
with_project do
generate "action web books#destroy --url=/books/:id --method=DELETE"
replace "apps/web/app.rb", "# sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']", "sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']"
server do
delete "/books/1", _csrf_token: "invalid"
expect(last_response.status).to eq(500)
end
end
end
end

View File

@ -1,29 +0,0 @@
# frozen_string_literal: true
RSpec.describe "force SSL", type: :integration do
it "forces SSL" do
project = "bookshelf_force_ssl"
with_project(project, server: :puma) do
generate "action web home#index --url=/"
inject_line_after "apps/web/app.rb", "configure do", "force_ssl true"
RSpec::Support::Env["HANAMI_ENV"] = "production"
RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
RSpec::Support::Env["SMTP_HOST"] = "localhost"
RSpec::Support::Env["SMTP_PORT"] = "25"
# key = Pathname.new(__dir__).join("..", "fixtures", "openssl", "server.key").realpath
# cert = Pathname.new(__dir__).join("..", "fixtures", "openssl", "server.crt").realpath
# bundle_exec "puma -b 'ssl://127.0.0.1:2300?key=#{key}&cert=#{cert}'" do
server do
# FIXME: I know, it's lame how I solved this problem, but I can't get Excon to do SSL handshake
expect do
get "/"
end.to raise_error(Excon::Error::Socket)
end
end
end
end

View File

@ -1,46 +0,0 @@
# frozen_string_literal: true
RSpec.describe "X-Content-Type-Options header", type: :integration do
it "returns default value" do
with_project do
generate "action web home#index --url=/"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["X-Content-Type-Options"]).to eq("nosniff")
end
end
end
it "returns custom value" do
with_project do
generate "action web home#index --url=/"
replace "apps/web/app.rb", "security.x_content_type_options 'nosniff'", "security.x_content_type_options 'foo'"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["X-Content-Type-Options"]).to eq("foo")
end
end
end
it "doesn't send header if setting is removed" do
with_project do
generate "action web home#index --url=/"
replace "apps/web/app.rb", "security.x_content_type_options 'nosniff'", ""
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers).to_not have_key("X-Content-Type-Options")
end
end
end
end

View File

@ -1,46 +0,0 @@
# frozen_string_literal: true
RSpec.describe "X-Frame-Options header", type: :integration do
it "returns default value" do
with_project do
generate "action web home#index --url=/"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["X-Frame-Options"]).to eq("DENY")
end
end
end
it "returns custom value" do
with_project do
generate "action web home#index --url=/"
replace "apps/web/app.rb", "security.x_frame_options 'DENY'", "security.x_frame_options 'ALLOW-FROM https://example.test/'"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["X-Frame-Options"]).to eq("ALLOW-FROM https://example.test/")
end
end
end
it "doesn't send header if setting is removed" do
with_project do
generate "action web home#index --url=/"
replace "apps/web/app.rb", "security.x_frame_options 'DENY'", ""
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers).to_not have_key("X-Frame-Options")
end
end
end
end

View File

@ -1,46 +0,0 @@
# frozen_string_literal: true
RSpec.describe "X-XSS-Protection header", type: :integration do
it "returns default value" do
with_project do
generate "action web home#index --url=/"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["X-XSS-Protection"]).to eq("1; mode=block")
end
end
end
it "returns custom value" do
with_project do
generate "action web home#index --url=/"
replace "apps/web/app.rb", "security.x_xss_protection '1; mode=block'", "security.x_xss_protection '0'"
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers["X-XSS-Protection"]).to eq("0")
end
end
end
it "doesn't send header if setting is removed" do
with_project do
generate "action web home#index --url=/"
replace "apps/web/app.rb", "security.x_xss_protection '1; mode=block'", ""
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.headers).to_not have_key("X-XSS-Protection")
end
end
end
end

View File

@ -1,51 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Send file", type: :integration do
it "sends file from the public directory" do
with_project do
write "public/static.txt", "Static file"
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
send_file "static.txt"
end
end
end
EOF
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.body).to include("Static file")
end
end
end
it "doesn't send file outside of public directory" do
with_project do
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
send_file __FILE__
end
end
end
EOF
server do
get "/"
expect(last_response.status).to eq(404)
end
end
end
end

View File

@ -1,247 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Sessions", type: :integration do
it "shows welcome page" do
with_project do
server do
visit "/"
expect(page).to have_title("Hanami | The web, with simplicity")
expect(page).to have_content("The web, with simplicity.")
expect(page).to have_content("Hanami is Open Source Software for MVC web development with Ruby.")
expect(page).to have_content("bundle exec hanami generate action web 'home#index' --url=/")
end
end
end
it "is empty by default" do
with_project do
prepare
server do
visit "/"
expect(current_path).to eq("/")
expect(page).to have_content("Sign in")
end
end
end
it "preserves data across requests" do
with_project do
prepare
server do
visit "/"
expect(current_path).to eq("/")
click_link("Sign in")
expect(current_path).to eq("/signin")
within "form#signin-form" do
click_button "Sign in"
end
visit "/" # Without this, Capybara losts the session.
expect(current_path).to eq("/")
expect(page).to have_content("Welcome, Luca")
end
end
end
it "clears the session" do
with_project do
prepare
server do
given_signedin_user
click_link "Sign out"
expect(current_path).to eq("/")
expect(page).to_not have_content("Welcome, Luca")
end
end
end
context "when sessions aren't enabled" do
it "raises error when trying to use `session'" do
with_project do
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = session[:foo]
end
end
end
EOF
server do
visit "/"
expect(page).to have_content("To use `session', please enable sessions for the current app.")
end
end
end
it "raises error when trying to use `flash'" do
with_project do
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = flash[:notice]
end
end
end
EOF
server do
visit "/"
expect(page).to have_content("To use `flash', please enable sessions for the current app.")
end
end
end
end
private
def prepare
# Enable sessions
replace "apps/web/app.rb", "# sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']", "sessions :cookie, secret: ENV['WEB_SESSIONS_SECRET']"
generate_user
generate_actions
end
def generate_user # rubocop:disable Metrics/MethodLength
generate_model "user"
generate_migration "create_users", <<~EOF
Hanami::Model.migration do
change do
create_table :users do
primary_key :id
column :name, String
end
execute "INSERT INTO users (name) VALUES('Luca')"
end
end
EOF
hanami "db prepare"
end
def generate_actions
generate_home
generate_signin
generate_signout
end
def generate_home # rubocop:disable Metrics/MethodLength
generate "action web home#index --url=/"
replace "apps/web/config/routes.rb", "home#index", 'root to: "home#index"'
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
expose :current_user
def call(params)
@current_user = UserRepository.new.find(session[:user_id])
end
end
end
EOF
rewrite "apps/web/templates/home/index.html.erb", <<~EOF
<h1>Bookshelf</h1>
<% if current_user.nil? %>
<%= link_to "Sign in", "/signin" %>
<% else %>
Welcome, <%= current_user.name %>
<%= link_to "Sign out", "/signout" %>
<% end %>
EOF
end
def generate_signin
generate_signin_new_action
generate_signin_create_action
end
def generate_signin_new_action
generate "action web sessions#new --url=/signin --method=GET"
rewrite "apps/web/templates/sessions/new.html.erb", <<~EOF
<h1>Sign in</h1>
<%=
form_for :signin, "/signin", id: "signin-form" do
div do
button "Sign in"
end
end
%>
EOF
end
def generate_signin_create_action
generate "action web sessions#create --url=/signin --method=POST"
rewrite "apps/web/controllers/sessions/create.rb", <<~EOF
module Web::Controllers::Sessions
class Create
include Web::Action
def call(params)
session[:user_id] = UserRepository.new.first.id
redirect_to routes.root_url
end
end
end
EOF
end
def generate_signout
generate "action web sessions#destroy --url=/signout --method=GET"
rewrite "apps/web/controllers/sessions/destroy.rb", <<~EOF
module Web::Controllers::Sessions
class Destroy
include Web::Action
def call(params)
session[:user_id] = nil
redirect_to routes.root_url
end
end
end
EOF
end
def given_signedin_user # rubocop:disable Metrics/AbcSize
visit "/"
expect(current_path).to eq("/")
click_link("Sign in")
expect(current_path).to eq("/signin")
within "form#signin-form" do
click_button "Sign in"
end
visit "/" # Without this, Capybara losts the session.
expect(current_path).to eq("/")
expect(page).to have_content("Welcome, Luca")
end
end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Static middleware", type: :integration do
it "serves a public file" do
with_project do
write "public/static.txt", "Static file"
RSpec::Support::Env["HANAMI_ENV"] = "production"
RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
RSpec::Support::Env["SERVE_STATIC_ASSETS"] = "true"
RSpec::Support::Env["SMTP_HOST"] = "localhost"
RSpec::Support::Env["SMTP_PORT"] = "25"
server do
visit "/static.txt"
expect(page.body).to include("Static file")
end
end
end
end

View File

@ -1,41 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Streaming", type: :integration do
xit "streams the body" do
with_project do
generate "action web home#index --url=/"
# Require Rack::Chunked
unshift "apps/web/app.rb", "require 'rack/chunked'"
# Mount middleware
replace "apps/web/app.rb", "# middleware.use", "middleware.use ::Rack::Chunked"
replace "apps/web/app.rb", "controller.prepare do", "controller.format text: 'text/plain'\ncontroller.prepare do"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.format = :text
self.body = Enumerator.new do |y|
%w(one two three).each { |s| y << s }
end
end
end
end
EOF
server do
get "/", {}, "HTTP_VERSION" => "HTTP/1.1"
expect(last_response.headers).to_not have_key("Content-Length")
expect(last_response.headers["Transfer-Encoding"]).to eq("chunked")
expect(last_response.status).to eq(200)
expect(last_response.body).to eq("3\r\none\r\n3\r\ntwo\r\n5\r\nthree\r\n0\r\n\r\n")
end
end
end
end

View File

@ -1,52 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Unsafe send file", type: :integration do
it "sends file from the public directory" do
with_project do
write "public/static.txt", "Static file"
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
unsafe_send_file "public/static.txt"
end
end
end
EOF
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.body).to include("Static file")
end
end
end
it "sends file outside of the public directory" do
with_project do
generate "action web home#index --url=/"
rewrite "apps/web/controllers/home/index.rb", <<~EOF
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
unsafe_send_file __FILE__
end
end
end
EOF
server do
get "/"
expect(last_response.status).to eq(200)
expect(last_response.body).to include("Web::Controllers::Home")
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More