Skip to content

Instantly share code, notes, and snippets.

@fernandes
Created June 25, 2019 12:53
Show Gist options
  • Save fernandes/0dc8b96bbcae715d7390472eeaadb9e2 to your computer and use it in GitHub Desktop.
Save fernandes/0dc8b96bbcae715d7390472eeaadb9e2 to your computer and use it in GitHub Desktop.
require "../spec_helper.cr"
describe AppServer do
visitor = AppVisitor.new # <<<--- check this!
it "signs in valid user" do
# create a user
user = UserBox.new.create
# visit sign in endpoint
visitor.post("/api/v1/sign_in", ({
"sign_in:email" => user.email,
"sign_in:password" => "pass1234",
}))
# check response has status: 200 and authorization header with "Bearer"
visitor.response.status_code.should eq 200
visitor.response.headers["Authorization"].should_not be_nil
end
it "creates user on sign up" do
visitor.post("/api/v1/sign_up", ({
"sign_up:name" => "New User",
"sign_up:email" => "[email protected]",
"sign_up:password" => "password",
"sign_up:password_confirmation" => "password",
}))
visitor.response.status_code.should eq 200
visitor.response.headers["Authorization"].should_not be_nil
UserQuery.new.email("[email protected]").first.should_not be_nil
end
end
require "http/client"
class AppVisitor
CSRF_TOKEN = "statictesttoken"
getter! response
@response : HTTP::Client::Response? = nil
def visit(path : String, headers : HTTP::Headers? = nil)
request = HTTP::Request.new("GET", path, headers)
@response = process_request(request)
end
def put(path : String, body : Hash(String, String))
request_with_body("PUT", path, with_csrf_token(body))
end
def post(path : String, body : Hash(String, String))
request_with_body("POST", path, with_csrf_token(body))
end
def response_body
body = @response.not_nil!.body
if body[0, 5] == "ERROR"
raise Exception.new(body)
else
JSON.parse(body)
end
end
private def with_csrf_token(body : Hash(String, String))
body[Lucky::ProtectFromForgery::PARAM_KEY] = CSRF_TOKEN
body
end
private def request_with_body(method : String, path : String, body : Hash(String, String))
body_strings = [] of String
body.each do |key, value|
body_strings << "#{URI.escape(key)}=#{URI.escape(value)}"
end
request = HTTP::Request.new(method, path, nil, body_strings.join("&"))
@response = process_request(request)
end
private def process_request(request)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
# context.session[Lucky::ProtectFromForgery::SESSION_KEY] = CSRF_TOKEN
middlewares.call context
response.close
io.rewind
HTTP::Client::Response.from_io(io, decompress: false)
end
def middlewares
HTTP::Server.build_middleware([
# Lucky::ForceSSLHandler.new,
Lucky::HttpMethodOverrideHandler.new,
Lucky::LogHandler.new,
# Disabled in API mode, but can be enabled if you need them:
# Lucky::SessionHandler.new,
# Lucky::FlashHandler.new,
Lucky::ErrorHandler.new(action: Errors::Show),
Lucky::RouteHandler.new,
# Disabled in API mode:
# Lucky::StaticFileHandler.new("./public", false),
Lucky::RouteNotFoundHandler.new,
])
end
module Matchers
def redirect_to(path : String)
RedirectToExpectation.new(path)
end
end
struct RedirectToExpectation
def initialize(@expected_path : String)
end
def match(visitor : AppVisitor)
visitor.response.status_code == 302 &&
visitor.response.headers.fetch("Location") == @expected_path
end
def failure_message(visitor : AppVisitor)
if visitor.response.headers.["Location"]?
"Expected to redirect to \"#{@expected_path}\", but redirected to #{visitor.response.headers.fetch("Location")}"
else
"Expected to redirect to \"#{@expected_path}\", but did not redirected at all"
end
end
end
def includes?(expected_value)
response.body.includes? expected_value
end
end
require "../../../../spec_helper"
include ContextHelper
private def params(merged = {} of String => String)
{
"task" => {
"id" => Cuid.generate,
"title" => "This is my first task",
}.merge!(merged),
}
end
def execute_action(params, user : User)
context = post(user: user, params: params)
response = Api::V1::Tasks::Create.new(context, {} of String => String).call
end
describe "Api::V1::Tasks::Create" do
it "creates a record" do
post_params = params
user = UserBox.create
response = execute_action(post_params, user: user)
json = JSON.parse(response.body)
json["id"].should eq(post_params["task"]["id"])
json["completed"].should eq(false)
end
it "acceptes completed value" do
post_params = params({"completed" => "true"})
post_params["task"].delete("id")
user = UserBox.create
response = execute_action(post_params, user: user)
json = JSON.parse(response.body)
Cuid.validate(json["id"].to_s).should be_truthy
json["completed"].should eq(true)
end
it "creates a tasklist then reuse it" do
post_params = params
post_params["task"].delete("id")
user = UserBox.create
# Create First Task
response = execute_action(post_params, user: user)
tasklist = TasklistQuery.new.user_id(user.id).first
TasklistQuery.new.user_id(user.id).size.should eq(1)
TaskQuery.new.tasklist_id(tasklist.id).size.should eq(1)
# Create Second Task
response = execute_action(post_params, user: user)
tasklist = TasklistQuery.new.user_id(user.id).first
TasklistQuery.new.user_id(user.id).size.should eq(1)
TaskQuery.new.tasklist_id(tasklist.id).size.should eq(2)
end
end
module ContextHelper
include Auth::GenerateToken
# My Methods
private def post(user : User, params = {} of String => String)
headers = build_headers({"Authorization" => generate_token(user)}, content_type: :json)
request = build_request body: params.to_json, headers: headers
build_context(request: request)
end
private def post(params = {} of String => String)
headers = build_headers(content_type: :json)
request = build_request body: params.to_json, headers: headers
build_context(request: request)
end
private def build_headers(headers = {} of String => String, content_type = nil)
building_headers = HTTP::Headers.new
headers.each do |k, v|
building_headers.add(k, v)
end
building_headers.add("Content-Type", "application/json") if content_type == :json
building_headers
end
# Lucky Methods
private def build_request(method = "GET", body = "", content_type = "")
headers = HTTP::Headers.new
headers.add("Content-Type", content_type)
HTTP::Request.new(method, "/", body: body, headers: headers)
end
private def build_request(method = "GET", body = "", content_type = "", headers = {} of String => String)
HTTP::Request.new(method, "/", body: body, headers: headers)
end
private def build_context(path = "/", request = nil) : HTTP::Server::Context
build_context_with_io(IO::Memory.new, path: path, request: request)
end
private def build_context(user : User, method = "GET", path = "/", request = nil) : HTTP::Server::Context
headers = HTTP::Headers.new
headers.add("Content-Type", "")
headers.add("Authorization", generate_token(user))
build_context_with_io(
IO::Memory.new,
path: "/",
request: build_request("GET", headers: headers),
)
end
private def build_context(method : String) : HTTP::Server::Context
build_context_with_io(
IO::Memory.new,
path: "/",
request: build_request(method)
)
end
private def build_context_with_io(io : IO, path = "/", request = nil) : HTTP::Server::Context
request = request || HTTP::Request.new("GET", path)
response = HTTP::Server::Response.new(io)
HTTP::Server::Context.new request, response
end
private def build_context_with_flash(flash : String)
build_context.tap do |context|
context.session.set(Lucky::FlashStore::SESSION_KEY, flash)
end
end
private def params
{} of String => String
end
end
require "jwt"
module Auth
module GenerateToken
def generate_token(user)
exp = (Time.utc + 14.days).to_unix
data = ({id: user.id, name: user.name, email: user.email}).to_s
payload = {"sub" => user.id, "user" => Base64.encode(data), "exp" => exp}
JWT.encode(payload, Lucky::Server.settings.secret_key_base, JWT::Algorithm::HS256)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment