tynn 2.0.0.alpha → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +168 -29
- data/lib/tynn.rb +0 -4
- data/lib/tynn/base.rb +368 -24
- data/lib/tynn/errors.rb +7 -0
- data/lib/tynn/json.rb +18 -5
- data/lib/tynn/render.rb +36 -25
- data/lib/tynn/request.rb +86 -15
- data/lib/tynn/response.rb +214 -12
- data/lib/tynn/secure_headers.rb +2 -6
- data/lib/tynn/session.rb +14 -5
- data/lib/tynn/ssl.rb +19 -16
- data/lib/tynn/test.rb +45 -38
- data/lib/tynn/utils.rb +15 -0
- data/lib/tynn/version.rb +1 -1
- data/test/default_headers_test.rb +1 -1
- data/test/environment_test.rb +1 -1
- data/test/helper.rb +9 -0
- data/test/json_test.rb +21 -6
- data/test/middleware_test.rb +23 -13
- data/test/plugin_test.rb +1 -1
- data/test/render_test.rb +24 -15
- data/test/request_headers_test.rb +8 -4
- data/test/request_test.rb +9 -0
- data/test/response_test.rb +217 -0
- data/test/routing_test.rb +128 -38
- data/test/secure_headers_test.rb +1 -1
- data/test/session_test.rb +6 -6
- data/test/settings_test.rb +3 -3
- data/test/ssl_test.rb +3 -3
- data/test/static_test.rb +1 -1
- metadata +14 -24
- data/lib/tynn/default_headers.rb +0 -50
- data/lib/tynn/settings.rb +0 -107
data/test/plugin_test.rb
CHANGED
data/test/render_test.rb
CHANGED
@@ -2,20 +2,17 @@
|
|
2
2
|
|
3
3
|
require_relative "helper"
|
4
4
|
require_relative "../lib/tynn/render"
|
5
|
-
require "tilt/erubis"
|
6
5
|
|
7
6
|
class RenderTest < Minitest::Test
|
8
|
-
VIEWS_PATH = File.expand_path("views", __dir__)
|
9
|
-
|
10
7
|
def setup
|
11
|
-
@app =
|
8
|
+
@app = new_app
|
12
9
|
|
13
|
-
@app.plugin(Tynn::Render,
|
10
|
+
@app.plugin(Tynn::Render, root: __dir__)
|
14
11
|
end
|
15
12
|
|
16
13
|
def test_partial
|
17
14
|
@app.define do
|
18
|
-
on
|
15
|
+
on "partial" do
|
19
16
|
res.write(partial("partial", name: "alice"))
|
20
17
|
end
|
21
18
|
end
|
@@ -23,12 +20,12 @@ class RenderTest < Minitest::Test
|
|
23
20
|
ts = Tynn::Test.new(@app)
|
24
21
|
ts.get("/partial")
|
25
22
|
|
26
|
-
assert_equal "alice", ts.res.body.strip
|
23
|
+
assert_equal "alice", ts.res.body.join.strip
|
27
24
|
end
|
28
25
|
|
29
26
|
def test_view
|
30
27
|
@app.define do
|
31
|
-
on
|
28
|
+
on "view" do
|
32
29
|
res.write(view("view", title: "welcome", name: "alice"))
|
33
30
|
end
|
34
31
|
end
|
@@ -36,12 +33,25 @@ class RenderTest < Minitest::Test
|
|
36
33
|
ts = Tynn::Test.new(@app)
|
37
34
|
ts.get("/view")
|
38
35
|
|
39
|
-
assert_equal "welcome / alice", ts.res.body.strip
|
36
|
+
assert_equal "welcome / alice", ts.res.body.join.strip
|
40
37
|
end
|
41
38
|
|
42
39
|
def test_render
|
43
40
|
@app.define do
|
44
|
-
on
|
41
|
+
on "render" do
|
42
|
+
render("view", title: "welcome", name: "alice")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
ts = Tynn::Test.new(@app)
|
47
|
+
ts.get("/render")
|
48
|
+
|
49
|
+
assert_equal "welcome / alice", ts.res.body.join.strip
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_render_content_type
|
53
|
+
@app.define do
|
54
|
+
on "render" do
|
45
55
|
render("view", title: "welcome", name: "alice")
|
46
56
|
end
|
47
57
|
end
|
@@ -50,12 +60,11 @@ class RenderTest < Minitest::Test
|
|
50
60
|
ts.get("/render")
|
51
61
|
|
52
62
|
assert_equal "text/html", ts.res.content_type
|
53
|
-
assert_equal "welcome / alice", ts.res.body.strip
|
54
63
|
end
|
55
64
|
|
56
65
|
def test_escaping
|
57
66
|
@app.define do
|
58
|
-
on
|
67
|
+
on "escape" do
|
59
68
|
res.write(partial("partial", name: "<a></a>"))
|
60
69
|
end
|
61
70
|
end
|
@@ -63,14 +72,14 @@ class RenderTest < Minitest::Test
|
|
63
72
|
ts = Tynn::Test.new(@app)
|
64
73
|
ts.get("/escape")
|
65
74
|
|
66
|
-
assert_equal "<a><
|
75
|
+
assert_equal "<a></a>", ts.res.body.join.strip
|
67
76
|
end
|
68
77
|
|
69
78
|
def test_custom_layout
|
70
79
|
@app.set(:render, layout: "custom_layout")
|
71
80
|
|
72
81
|
@app.define do
|
73
|
-
get do
|
82
|
+
on get do
|
74
83
|
render("view", title: "welcome", name: "alice")
|
75
84
|
end
|
76
85
|
end
|
@@ -78,6 +87,6 @@ class RenderTest < Minitest::Test
|
|
78
87
|
ts = Tynn::Test.new(@app)
|
79
88
|
ts.get("/")
|
80
89
|
|
81
|
-
assert_equal "custom / welcome / alice", ts.res.body.strip
|
90
|
+
assert_equal "custom / welcome / alice", ts.res.body.join.strip
|
82
91
|
end
|
83
92
|
end
|
@@ -4,14 +4,18 @@ require_relative "helper"
|
|
4
4
|
|
5
5
|
class RequestHeadersTest < Minitest::Test
|
6
6
|
def setup
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
@headers = Tynn::Request::Headers.new(
|
8
|
+
"HTTP_HOST" => "127.0.0.1",
|
9
|
+
"CONTENT_TYPE" => "text/plain"
|
10
|
+
)
|
11
11
|
end
|
12
12
|
|
13
13
|
def test_get_header
|
14
14
|
assert_equal "127.0.0.1", @headers["Host"]
|
15
|
+
assert_equal "127.0.0.1", @headers["host"]
|
16
|
+
assert_equal "text/plain", @headers["Content-Type"]
|
17
|
+
assert_equal "text/plain", @headers["content-type"]
|
18
|
+
assert_equal "text/plain", @headers["CONTENT_TYPE"]
|
15
19
|
end
|
16
20
|
|
17
21
|
def test_check_if_key_exists
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class ResponseTest < Minitest::Test
|
4
|
+
def test_new
|
5
|
+
res = Tynn::Response.new
|
6
|
+
|
7
|
+
assert_nil res.status
|
8
|
+
assert_empty res.headers
|
9
|
+
assert_empty res.body
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_new_with_headers
|
13
|
+
res = Tynn::Response.new("Content-Type" => "text/plain")
|
14
|
+
|
15
|
+
assert_equal "text/plain", res.headers["Content-Type"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_status
|
19
|
+
res = Tynn::Response.new
|
20
|
+
|
21
|
+
res.status = 200
|
22
|
+
|
23
|
+
assert_equal 200, res.status
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_headers
|
27
|
+
res = Tynn::Response.new
|
28
|
+
|
29
|
+
res.headers["Content-Type"] = "text/plain"
|
30
|
+
|
31
|
+
assert_equal "text/plain", res.headers["Content-Type"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_content_length
|
35
|
+
res = Tynn::Response.new
|
36
|
+
|
37
|
+
res.headers["Content-Length"] = "42"
|
38
|
+
|
39
|
+
assert_equal 42, res.content_length
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_content_type
|
43
|
+
res = Tynn::Response.new
|
44
|
+
|
45
|
+
res.content_type = "text/html"
|
46
|
+
|
47
|
+
assert_equal "text/html", res.headers["Content-Type"]
|
48
|
+
assert_equal "text/html", res.content_type
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_location
|
52
|
+
res = Tynn::Response.new
|
53
|
+
|
54
|
+
res.location = "/users"
|
55
|
+
|
56
|
+
assert_equal "/users", res.headers["Location"]
|
57
|
+
assert_equal "/users", res.location
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_write_body
|
61
|
+
res = Tynn::Response.new
|
62
|
+
|
63
|
+
res.write("foo")
|
64
|
+
res.write("bar")
|
65
|
+
|
66
|
+
assert_equal ["foo", "bar"], res.body
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_write_content_length
|
70
|
+
res = Tynn::Response.new
|
71
|
+
|
72
|
+
res.write("foo")
|
73
|
+
res.write("bar")
|
74
|
+
|
75
|
+
assert_equal "6", res.headers["Content-Length"]
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_finish_without_status_and_body
|
79
|
+
res = Tynn::Response.new
|
80
|
+
|
81
|
+
status, _, body = res.finish
|
82
|
+
|
83
|
+
assert_equal 404, status
|
84
|
+
assert_empty body
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_finish_without_status_but_body
|
88
|
+
res = Tynn::Response.new
|
89
|
+
|
90
|
+
res.write("hei!")
|
91
|
+
|
92
|
+
status, _, body = res.finish
|
93
|
+
|
94
|
+
assert_equal 200, status
|
95
|
+
assert_equal ["hei!"], body
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_redirect_with_path
|
99
|
+
res = Tynn::Response.new
|
100
|
+
|
101
|
+
res.redirect("/path")
|
102
|
+
|
103
|
+
assert_equal 302, res.status
|
104
|
+
assert_equal "/path", res.location
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_redirect_with_url
|
108
|
+
res = Tynn::Response.new
|
109
|
+
|
110
|
+
res.redirect("https://google.com")
|
111
|
+
|
112
|
+
assert_equal 302, res.status
|
113
|
+
assert_equal "https://google.com", res.location
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_redirect_with_status
|
117
|
+
res = Tynn::Response.new
|
118
|
+
|
119
|
+
res.redirect("/path", 303)
|
120
|
+
|
121
|
+
assert_equal 303, res.status
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_set_cookie
|
125
|
+
res = Tynn::Response.new
|
126
|
+
|
127
|
+
res.set_cookie("foo", "bar")
|
128
|
+
|
129
|
+
assert_equal "foo=bar", res.headers["Set-Cookie"]
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_set_multiple_cookies
|
133
|
+
res = Tynn::Response.new
|
134
|
+
|
135
|
+
res.set_cookie("foo1", "bar1")
|
136
|
+
res.set_cookie("foo2", "bar2")
|
137
|
+
|
138
|
+
assert_equal "foo1=bar1\nfoo2=bar2", res.headers["Set-Cookie"]
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_set_cookie_with_options
|
142
|
+
res = Tynn::Response.new
|
143
|
+
|
144
|
+
time = Time.new(2016)
|
145
|
+
|
146
|
+
res.set_cookie("foo", "bar", {
|
147
|
+
domain: "example.org",
|
148
|
+
path: "/path",
|
149
|
+
max_age: 100, # seconds
|
150
|
+
expires: time,
|
151
|
+
httponly: true,
|
152
|
+
secure: true,
|
153
|
+
same_site: :Lax
|
154
|
+
})
|
155
|
+
|
156
|
+
expected = %W(
|
157
|
+
foo=bar
|
158
|
+
domain=example.org
|
159
|
+
path=/path
|
160
|
+
max-age=100
|
161
|
+
expires=#{ time.gmtime.rfc2822 }
|
162
|
+
secure
|
163
|
+
HttpOnly
|
164
|
+
SameSite=Lax
|
165
|
+
).join("; ")
|
166
|
+
|
167
|
+
assert_equal expected, res.headers["Set-Cookie"]
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_delete_cookie
|
171
|
+
res = Tynn::Response.new
|
172
|
+
|
173
|
+
res.set_cookie("foo1", "bar1")
|
174
|
+
res.set_cookie("foo2", "bar2")
|
175
|
+
|
176
|
+
res.delete_cookie("foo1")
|
177
|
+
|
178
|
+
expected = [
|
179
|
+
"foo2=bar2",
|
180
|
+
"foo1=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
|
181
|
+
].join("\n")
|
182
|
+
|
183
|
+
assert_equal expected, res.headers["Set-Cookie"]
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_delete_cookie_with_multiple_domains
|
187
|
+
res = Tynn::Response.new
|
188
|
+
|
189
|
+
res.set_cookie("foo1", "bar1", domain: "foo1.example.org")
|
190
|
+
res.set_cookie("foo2", "bar2", domain: "foo2.example.org")
|
191
|
+
|
192
|
+
res.delete_cookie("foo1", domain: "foo1.example.org")
|
193
|
+
|
194
|
+
expected = [
|
195
|
+
"foo2=bar2; domain=foo2.example.org",
|
196
|
+
"foo1=; domain=foo1.example.org; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
|
197
|
+
].join("\n")
|
198
|
+
|
199
|
+
assert_equal expected, res.headers["Set-Cookie"]
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_delete_cookie_with_same_domain_and_multiple_paths
|
203
|
+
res = Tynn::Response.new
|
204
|
+
|
205
|
+
res.set_cookie("foo1", "bar1", domain: "example.org", path: "/foo1")
|
206
|
+
res.set_cookie("foo2", "bar2", domain: "example.org", path: "/foo2")
|
207
|
+
|
208
|
+
res.delete_cookie("foo1", domain: "example.org", path: "/foo1")
|
209
|
+
|
210
|
+
expected = [
|
211
|
+
"foo2=bar2; domain=example.org; path=/foo2",
|
212
|
+
"foo1=; domain=example.org; path=/foo1; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
|
213
|
+
].join("\n")
|
214
|
+
|
215
|
+
assert_equal expected, res.headers["Set-Cookie"]
|
216
|
+
end
|
217
|
+
end
|
data/test/routing_test.rb
CHANGED
@@ -4,17 +4,31 @@ require_relative "helper"
|
|
4
4
|
|
5
5
|
class RoutingTest < Minitest::Test
|
6
6
|
def setup
|
7
|
-
@app =
|
7
|
+
@app = new_app
|
8
8
|
end
|
9
9
|
|
10
10
|
def test_raise_if_not_handler
|
11
|
-
assert_raises { @app.call({}) }
|
11
|
+
assert_raises(Tynn::Error) { @app.call({}) }
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_path_matching
|
15
15
|
@app.define do
|
16
|
-
on "
|
17
|
-
res.write("
|
16
|
+
on "path" do
|
17
|
+
res.write("/path")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ts = Tynn::Test.new(@app)
|
22
|
+
ts.get("/path")
|
23
|
+
|
24
|
+
assert_equal 200, ts.res.status
|
25
|
+
assert_equal "/path", ts.res.body.join
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_path_dont_match
|
29
|
+
@app.define do
|
30
|
+
on "private" do
|
31
|
+
res.write("nothing to see here")
|
18
32
|
end
|
19
33
|
end
|
20
34
|
|
@@ -22,90 +36,166 @@ class RoutingTest < Minitest::Test
|
|
22
36
|
ts.get("/")
|
23
37
|
|
24
38
|
assert_equal 404, ts.res.status
|
39
|
+
assert_empty ts.res.body
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_capture
|
43
|
+
@app.define do
|
44
|
+
on :foo do |foo|
|
45
|
+
on :bar do |bar|
|
46
|
+
res.write(sprintf("%s:%s", foo, bar))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
25
50
|
|
26
|
-
ts.
|
51
|
+
ts = Tynn::Test.new(@app)
|
52
|
+
ts.get("/foo/bar")
|
27
53
|
|
28
54
|
assert_equal 200, ts.res.status
|
29
|
-
assert_equal "
|
55
|
+
assert_equal "foo:bar", ts.res.body.join
|
30
56
|
end
|
31
57
|
|
32
|
-
def
|
33
|
-
|
58
|
+
def test_capture_root
|
59
|
+
@app.define do
|
60
|
+
on :foo do |foo|
|
61
|
+
res.write(foo)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
ts = Tynn::Test.new(@app)
|
66
|
+
ts.get("/")
|
34
67
|
|
35
|
-
|
36
|
-
|
37
|
-
|
68
|
+
assert_equal 404, ts.res.status
|
69
|
+
assert_empty ts.res.body
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_path_and_verbs
|
73
|
+
methods = %w(get post patch put delete)
|
74
|
+
|
75
|
+
@app.define do
|
76
|
+
methods.each do |method|
|
77
|
+
on method do
|
78
|
+
on send(method) do
|
79
|
+
res.write(method)
|
80
|
+
end
|
81
|
+
end
|
38
82
|
end
|
83
|
+
end
|
39
84
|
|
40
|
-
|
41
|
-
|
85
|
+
ts = Tynn::Test.new(@app)
|
86
|
+
|
87
|
+
methods.each do |method|
|
88
|
+
ts.send(method, "/#{ method }")
|
42
89
|
|
43
90
|
assert_equal 200, ts.res.status
|
44
|
-
assert_equal method
|
91
|
+
assert_equal method, ts.res.body.join
|
45
92
|
end
|
46
93
|
end
|
47
94
|
|
48
|
-
def
|
95
|
+
def test_verbs_on_root
|
49
96
|
@app.define do
|
50
|
-
on
|
51
|
-
|
52
|
-
res.write(
|
97
|
+
on "foo" do
|
98
|
+
get do
|
99
|
+
res.write("nothing to see here")
|
53
100
|
end
|
54
101
|
end
|
55
102
|
end
|
56
103
|
|
57
104
|
ts = Tynn::Test.new(@app)
|
58
|
-
ts.get("/
|
105
|
+
ts.get("/foo/bar/baz")
|
59
106
|
|
60
|
-
assert_equal
|
61
|
-
|
107
|
+
assert_equal 404, ts.res.status
|
108
|
+
assert_empty ts.res.body
|
62
109
|
end
|
63
110
|
|
64
111
|
def test_composition
|
65
|
-
|
112
|
+
admin = new_app
|
113
|
+
|
114
|
+
admin.define do
|
115
|
+
on get do
|
116
|
+
res.write("/admin")
|
117
|
+
end
|
118
|
+
end
|
66
119
|
|
67
120
|
@app.define do
|
68
|
-
on
|
69
|
-
run(
|
121
|
+
on "admin" do
|
122
|
+
run(admin)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
ts = Tynn::Test.new(@app)
|
127
|
+
ts.get("/admin")
|
128
|
+
|
129
|
+
assert_equal 200, ts.res.status
|
130
|
+
assert_equal "/admin", ts.res.body.join
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_composition_with_inbox
|
134
|
+
foo = new_app
|
135
|
+
bar = new_app
|
136
|
+
baz = new_app
|
137
|
+
|
138
|
+
@app.define do
|
139
|
+
on "foo" do
|
140
|
+
run(foo, foo: "foo")
|
70
141
|
end
|
71
142
|
end
|
72
143
|
|
73
144
|
foo.define do
|
74
|
-
|
75
|
-
|
145
|
+
on "bar" do
|
146
|
+
run(bar, inbox.merge(bar: "bar"))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
bar.define do
|
151
|
+
on "baz" do
|
152
|
+
run(baz, inbox.merge(baz: "baz"))
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
baz.define do
|
157
|
+
on get do
|
158
|
+
res.write(sprintf("%{foo}/%{bar}/%{baz}", inbox))
|
76
159
|
end
|
77
160
|
end
|
78
161
|
|
79
162
|
ts = Tynn::Test.new(@app)
|
80
|
-
ts.get("/foo")
|
163
|
+
ts.get("/foo/bar/baz")
|
81
164
|
|
82
165
|
assert_equal 200, ts.res.status
|
83
|
-
assert_equal "
|
166
|
+
assert_equal "foo/bar/baz", ts.res.body.join
|
84
167
|
end
|
85
168
|
|
86
|
-
def
|
169
|
+
def test_run_rack_app
|
87
170
|
@app.define do
|
88
|
-
|
89
|
-
|
90
|
-
|
171
|
+
run(proc { |_|
|
172
|
+
[200, { "Content-Type" => "text/plain", "Content-Length" => "4" }, ["hei!"]]
|
173
|
+
})
|
91
174
|
end
|
92
175
|
|
93
176
|
ts = Tynn::Test.new(@app)
|
94
177
|
ts.get("/")
|
95
178
|
|
96
|
-
assert_equal
|
179
|
+
assert_equal 200, ts.res.status
|
180
|
+
assert_equal "hei!", ts.res.body.join
|
181
|
+
assert_equal 4, ts.res.content_length
|
182
|
+
assert_equal "text/plain", ts.res.content_type
|
97
183
|
end
|
98
184
|
|
99
|
-
def
|
185
|
+
def test_halt
|
100
186
|
@app.define do
|
101
|
-
|
102
|
-
|
103
|
-
|
187
|
+
res.status = 401
|
188
|
+
res.write("Unauthorized")
|
189
|
+
|
190
|
+
halt(res.finish)
|
191
|
+
|
192
|
+
raise "nothing to see here"
|
104
193
|
end
|
105
194
|
|
106
195
|
ts = Tynn::Test.new(@app)
|
107
196
|
ts.get("/")
|
108
197
|
|
109
|
-
assert_equal
|
198
|
+
assert_equal 401, ts.res.status
|
199
|
+
assert_equal "Unauthorized", ts.res.body.join
|
110
200
|
end
|
111
201
|
end
|