/
spec_helper.cr
113 lines (94 loc) · 3.46 KB
/
spec_helper.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
require "../action-controller"
require "hot_topic"
module ActionController
class SpecHelper
include Router
getter route_handler = RouteHandler.new
def initialize
init_routes
end
private def init_routes
{% for klass in ActionController::Base::CONCRETE_CONTROLLERS %}
{{klass}}.__init_routes__(self)
{% end %}
end
def hot_topic
HotTopic.new(@route_handler)
end
def self.client
ActionController::SpecHelper.new.hot_topic
end
# :nodoc:
module ContextHelper
macro included
macro inherited
# Simplify obtaining an instance of a controller class in specs
def self.spec_instance(request : HTTP::Request = HTTP::Request.new("GET", "/"))
response = HTTP::Server::Response.new(IO::Memory.new, request.version)
context = HTTP::Server::Context.new request, response
context.response.output = IO::Memory.new
method = request.method
req_path = request.path
search_path = "#{method}#{req_path}"
ActionController::SpecHelper.new.route_handler.search_route(method, req_path, search_path, context)
self.new(context)
end
end
end
end
end
abstract class Base
# extend the action controller classes with the `with_state` helper
include SpecHelper::ContextHelper
end
end
# Extend hot topic for establishing a websocket request
class HotTopic::Client(T) < HTTP::Client
def establish_ws(uri : URI | String, headers = HTTP::Headers.new) : HTTP::WebSocket
# build bi-directional io
local_read, remote_write = IO.pipe
remote_read, local_write = IO.pipe
local_io = IO::Stapled.new(local_read, local_write)
remote_io = IO::Stapled.new(remote_read, remote_write)
begin
random_key = Base64.strict_encode(StaticArray(UInt8, 16).new { rand(256).to_u8 })
headers["Connection"] = "Upgrade"
headers["Upgrade"] = "websocket"
headers["Sec-WebSocket-Version"] = HTTP::WebSocket::Protocol::VERSION
headers["Sec-WebSocket-Key"] = random_key
case uri
in URI
if (user = uri.user) && (password = uri.password)
headers["Authorization"] ||= "Basic #{Base64.strict_encode("#{user}:#{password}")}"
end
path = uri.request_target
in String
path = uri
end
handshake = HTTP::Request.new("GET", path, headers)
response = HTTP::Server::Response.new(remote_io)
context = HTTP::Server::Context.new(handshake, response)
context.response.output = remote_io
# emulate the upgrade request processing
spawn do
@app.call(context)
if upgrade_handler = response.upgrade_handler
upgrade_handler.call(remote_io)
end
end
handshake_response = HTTP::Client::Response.from_io(local_io, ignore_body: true)
unless handshake_response.status.switching_protocols?
raise Socket::Error.new("Handshake got denied. Status code was #{handshake_response.status.code}.")
end
challenge_response = HTTP::WebSocket::Protocol.key_challenge(random_key)
unless handshake_response.headers["Sec-WebSocket-Accept"]? == challenge_response
raise Socket::Error.new("Handshake got denied. Server did not verify WebSocket challenge.")
end
rescue error
local_io.close
remote_io.close
raise error
end
HTTP::WebSocket.new HTTP::WebSocket::Protocol.new(local_io, masked: true)
end
end