hanami/spec/integration/cli/server_spec.rb

627 lines
17 KiB
Ruby

# 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