goofy 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gems +4 -0
- data/.gitignore +2 -0
- data/CHANGELOG +47 -0
- data/CONTRIBUTING +19 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +67 -0
- data/app/.rspec +2 -0
- data/app/Gemfile +13 -0
- data/app/app/controllers/application_controller.rb +3 -0
- data/app/app/services/.keep +0 -0
- data/app/config.ru +4 -0
- data/app/config/environment.rb +9 -0
- data/app/config/initializers/.keep +0 -0
- data/app/config/routes.rb +7 -0
- data/app/config/settings.rb +0 -0
- data/app/spec/helpers.rb +2 -0
- data/app/spec/helpers/goofy.rb +11 -0
- data/app/spec/spec_helper.rb +107 -0
- data/benchmark/measure.rb +35 -0
- data/bin/check_its_goofy.rb +15 -0
- data/bin/goofy +61 -0
- data/bin/goofy_generator.rb +357 -0
- data/bin/goofy_instance_creator.rb +40 -0
- data/examples/config.ru +18 -0
- data/examples/measure.rb +17 -0
- data/examples/rack-response.ru +21 -0
- data/examples/views/home.mote +7 -0
- data/examples/views/layout.mote +11 -0
- data/goofy.gemspec +26 -0
- data/lib/goofy.rb +405 -0
- data/lib/goofy/capybara.rb +13 -0
- data/lib/goofy/controller.rb +14 -0
- data/lib/goofy/controller/base.rb +21 -0
- data/lib/goofy/controller/callbacks.rb +19 -0
- data/lib/goofy/render.rb +63 -0
- data/lib/goofy/router.rb +9 -0
- data/lib/goofy/safe.rb +23 -0
- data/lib/goofy/safe/csrf.rb +47 -0
- data/lib/goofy/safe/secure_headers.rb +40 -0
- data/lib/goofy/test.rb +11 -0
- data/makefile +4 -0
- data/test/accept.rb +32 -0
- data/test/captures.rb +162 -0
- data/test/composition.rb +69 -0
- data/test/controller.rb +29 -0
- data/test/cookie.rb +34 -0
- data/test/csrf.rb +139 -0
- data/test/extension.rb +21 -0
- data/test/helper.rb +11 -0
- data/test/host.rb +29 -0
- data/test/integration.rb +114 -0
- data/test/match.rb +86 -0
- data/test/middleware.rb +46 -0
- data/test/number.rb +36 -0
- data/test/on.rb +157 -0
- data/test/param.rb +66 -0
- data/test/path.rb +86 -0
- data/test/plugin.rb +68 -0
- data/test/rack.rb +22 -0
- data/test/redirect.rb +21 -0
- data/test/render.rb +128 -0
- data/test/root.rb +83 -0
- data/test/run.rb +23 -0
- data/test/safe.rb +74 -0
- data/test/segment.rb +45 -0
- data/test/session.rb +21 -0
- data/test/settings.rb +52 -0
- data/test/views/about.erb +1 -0
- data/test/views/about.str +1 -0
- data/test/views/content-yield.erb +1 -0
- data/test/views/custom/abs_path.mote +1 -0
- data/test/views/frag.mote +1 -0
- data/test/views/home.erb +2 -0
- data/test/views/home.mote +1 -0
- data/test/views/home.str +2 -0
- data/test/views/layout-alternative.erb +2 -0
- data/test/views/layout-yield.erb +3 -0
- data/test/views/layout.erb +2 -0
- data/test/views/layout.mote +2 -0
- data/test/views/layout.str +2 -0
- data/test/views/test.erb +1 -0
- data/test/with.rb +42 -0
- metadata +271 -0
data/test/plugin.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
scope do
|
4
|
+
module Helper
|
5
|
+
def clean(str)
|
6
|
+
str.strip
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
test do
|
11
|
+
Goofy.plugin Helper
|
12
|
+
|
13
|
+
Goofy.define do
|
14
|
+
on default do
|
15
|
+
res.write clean " foo "
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
_, _, body = Goofy.call({})
|
20
|
+
|
21
|
+
assert_response body, ["foo"]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
scope do
|
26
|
+
module Number
|
27
|
+
def num
|
28
|
+
1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Plugin
|
33
|
+
def self.setup(app)
|
34
|
+
app.plugin Number
|
35
|
+
end
|
36
|
+
|
37
|
+
def bar
|
38
|
+
"baz"
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
def foo
|
43
|
+
"bar"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
setup do
|
49
|
+
Goofy.plugin Plugin
|
50
|
+
|
51
|
+
Goofy.define do
|
52
|
+
on default do
|
53
|
+
res.write bar
|
54
|
+
res.write num
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
test do
|
60
|
+
assert_equal "bar", Goofy.foo
|
61
|
+
end
|
62
|
+
|
63
|
+
test do
|
64
|
+
_, _, body = Goofy.call({})
|
65
|
+
|
66
|
+
assert_response body, ["baz", "1"]
|
67
|
+
end
|
68
|
+
end
|
data/test/rack.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
require "goofy/test"
|
3
|
+
|
4
|
+
scope do
|
5
|
+
test do
|
6
|
+
Goofy.define do
|
7
|
+
on root do
|
8
|
+
res.write "home"
|
9
|
+
end
|
10
|
+
|
11
|
+
on "about" do
|
12
|
+
res.write "about"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
get "/"
|
17
|
+
assert_equal "home", last_response.body
|
18
|
+
|
19
|
+
get "/about"
|
20
|
+
assert_equal "about", last_response.body
|
21
|
+
end
|
22
|
+
end
|
data/test/redirect.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
test "redirect" do
|
4
|
+
Goofy.define do
|
5
|
+
on "hello" do
|
6
|
+
res.write "hello, world"
|
7
|
+
end
|
8
|
+
|
9
|
+
on "" do
|
10
|
+
res.redirect "/hello"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
env = { "SCRIPT_NAME" => "/", "PATH_INFO" => "/" }
|
15
|
+
|
16
|
+
status, headers, body = Goofy.call(env)
|
17
|
+
|
18
|
+
assert_equal status, 302
|
19
|
+
assert_equal headers, { "Location" => "/hello" }
|
20
|
+
assert_response body, []
|
21
|
+
end
|
data/test/render.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
require "goofy/render"
|
4
|
+
|
5
|
+
test "doesn't override the settings if they already exist" do
|
6
|
+
Goofy.settings[:render] = {
|
7
|
+
:views => "./test/views",
|
8
|
+
:template_engine => "haml"
|
9
|
+
}
|
10
|
+
|
11
|
+
Goofy.plugin Goofy::Render
|
12
|
+
|
13
|
+
assert_equal "./test/views", Goofy.settings[:render][:views]
|
14
|
+
assert_equal "haml", Goofy.settings[:render][:template_engine]
|
15
|
+
end
|
16
|
+
|
17
|
+
scope do
|
18
|
+
setup do
|
19
|
+
Goofy.plugin Goofy::Render
|
20
|
+
Goofy.settings[:render][:views] = "./test/views"
|
21
|
+
Goofy.settings[:render][:template_engine] = "erb"
|
22
|
+
|
23
|
+
Goofy.define do
|
24
|
+
on "home" do
|
25
|
+
res.write view("home", name: "Agent Smith", title: "Home")
|
26
|
+
end
|
27
|
+
|
28
|
+
on "about" do
|
29
|
+
res.write partial("about", title: "About Goofy")
|
30
|
+
end
|
31
|
+
|
32
|
+
on "render" do
|
33
|
+
render("about", title: "About Goofy")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
test "partial" do
|
39
|
+
_, _, body = Goofy.call({ "PATH_INFO" => "/about", "SCRIPT_NAME" => "/" })
|
40
|
+
|
41
|
+
assert_response body, ["<h1>About Goofy</h1>"]
|
42
|
+
end
|
43
|
+
|
44
|
+
test "view" do
|
45
|
+
_, _, body = Goofy.call({ "PATH_INFO" => "/home", "SCRIPT_NAME" => "/" })
|
46
|
+
|
47
|
+
assert_response body, ["<title>Goofy: Home</title>\n<h1>Home</h1>\n<p>Hello Agent Smith</p>"]
|
48
|
+
end
|
49
|
+
|
50
|
+
test "render" do
|
51
|
+
_, _, body = Goofy.call({ "PATH_INFO" => "/render", "SCRIPT_NAME" => "/" })
|
52
|
+
|
53
|
+
assert_response body, ["<title>Goofy: About Goofy</title>\n<h1>About Goofy</h1>"]
|
54
|
+
end
|
55
|
+
|
56
|
+
test "partial with str as engine" do
|
57
|
+
Goofy.settings[:render][:template_engine] = "str"
|
58
|
+
|
59
|
+
_, _, body = Goofy.call({ "PATH_INFO" => "/about", "SCRIPT_NAME" => "/" })
|
60
|
+
|
61
|
+
assert_response body, ["<h1>About Goofy</h1>"]
|
62
|
+
end
|
63
|
+
|
64
|
+
test "view with str as engine" do
|
65
|
+
Goofy.settings[:render][:template_engine] = "str"
|
66
|
+
|
67
|
+
_, _, body = Goofy.call({ "PATH_INFO" => "/home", "SCRIPT_NAME" => "/" })
|
68
|
+
|
69
|
+
assert_response body, ["<title>Goofy: Home</title>\n<h1>Home</h1>\n<p>Hello Agent Smith</p>"]
|
70
|
+
end
|
71
|
+
|
72
|
+
test "custom default layout support" do
|
73
|
+
Goofy.settings[:render][:layout] = "layout-alternative"
|
74
|
+
|
75
|
+
_, _, body = Goofy.call({ "PATH_INFO" => "/home", "SCRIPT_NAME" => "/" })
|
76
|
+
|
77
|
+
assert_response body, ["<title>Alternative Layout: Home</title>\n<h1>Home</h1>\n<p>Hello Agent Smith</p>"]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
test "caching behavior" do
|
82
|
+
Thread.current[:_cache] = nil
|
83
|
+
|
84
|
+
Goofy.plugin Goofy::Render
|
85
|
+
Goofy.settings[:render][:views] = "./test/views"
|
86
|
+
|
87
|
+
Goofy.define do
|
88
|
+
on "foo/:i" do |i|
|
89
|
+
res.write partial("test", title: i)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
10.times do |i|
|
94
|
+
_, _, resp = Goofy.call({ "PATH_INFO" => "/foo/#{i}", "SCRIPT_NAME" => "" })
|
95
|
+
end
|
96
|
+
|
97
|
+
assert_equal 1, Thread.current[:_cache].instance_variable_get(:@cache).size
|
98
|
+
end
|
99
|
+
|
100
|
+
test "overrides layout" do
|
101
|
+
Goofy.plugin Goofy::Render
|
102
|
+
Goofy.settings[:render][:views] = "./test/views"
|
103
|
+
|
104
|
+
Goofy.define do
|
105
|
+
on true do
|
106
|
+
res.write view("home", { name: "Agent Smith", title: "Home" }, "layout-alternative")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
_, _, body = Goofy.call({})
|
111
|
+
|
112
|
+
assert_response body, ["<title>Alternative Layout: Home</title>\n<h1>Home</h1>\n<p>Hello Agent Smith</p>"]
|
113
|
+
end
|
114
|
+
|
115
|
+
test "ensures content-type header is set" do
|
116
|
+
Goofy.plugin(Goofy::Render)
|
117
|
+
|
118
|
+
Goofy.define do
|
119
|
+
on default do
|
120
|
+
res.status = 403
|
121
|
+
render("about", title: "Hello Goofy")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
_, headers, _ = Goofy.call({})
|
126
|
+
|
127
|
+
assert_equal("text/html; charset=utf-8", headers["Content-Type"])
|
128
|
+
end
|
data/test/root.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
test "matching an empty segment" do
|
4
|
+
Goofy.define do
|
5
|
+
on "" do
|
6
|
+
res.write req.path
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
env = {
|
11
|
+
"SCRIPT_NAME" => "",
|
12
|
+
"PATH_INFO" => "/"
|
13
|
+
}
|
14
|
+
|
15
|
+
_, _, resp = Goofy.call(env)
|
16
|
+
|
17
|
+
assert_response resp, ["/"]
|
18
|
+
end
|
19
|
+
|
20
|
+
test "nested empty segments" do
|
21
|
+
Goofy.define do
|
22
|
+
on "" do
|
23
|
+
on "" do
|
24
|
+
on "1" do
|
25
|
+
res.write "IT WORKS!"
|
26
|
+
res.write req.path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
env = {
|
33
|
+
"SCRIPT_NAME" => "",
|
34
|
+
"PATH_INFO" => "///1"
|
35
|
+
}
|
36
|
+
|
37
|
+
_, _, resp = Goofy.call(env)
|
38
|
+
|
39
|
+
assert_response resp, ["IT WORKS!", "///1"]
|
40
|
+
end
|
41
|
+
|
42
|
+
test "/events/? scenario" do
|
43
|
+
class Events < Goofy
|
44
|
+
define do
|
45
|
+
on root do
|
46
|
+
res.write "Hooray"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Goofy.define do
|
52
|
+
on "events" do
|
53
|
+
run Events
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
env = {
|
58
|
+
"SCRIPT_NAME" => "",
|
59
|
+
"PATH_INFO" => "/events"
|
60
|
+
}
|
61
|
+
|
62
|
+
_, _, resp = Goofy.call(env)
|
63
|
+
|
64
|
+
assert_response resp, ["Hooray"]
|
65
|
+
|
66
|
+
env = {
|
67
|
+
"SCRIPT_NAME" => "",
|
68
|
+
"PATH_INFO" => "/events/"
|
69
|
+
}
|
70
|
+
|
71
|
+
_, _, resp = Goofy.call(env)
|
72
|
+
|
73
|
+
assert_response resp, ["Hooray"]
|
74
|
+
|
75
|
+
env = {
|
76
|
+
"SCRIPT_NAME" => "",
|
77
|
+
"PATH_INFO" => "/events/a"
|
78
|
+
}
|
79
|
+
|
80
|
+
_, _, resp = Goofy.call(env)
|
81
|
+
|
82
|
+
assert_response resp, []
|
83
|
+
end
|
data/test/run.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
test "redirect canonical example" do
|
4
|
+
Goofy.define do
|
5
|
+
def redirect(*args)
|
6
|
+
run Goofy.new { on(true) { res.redirect(*args) }}
|
7
|
+
end
|
8
|
+
|
9
|
+
on "account" do
|
10
|
+
redirect "/login", 307
|
11
|
+
|
12
|
+
res.write "Super secure content"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
env = { "SCRIPT_NAME" => "/", "PATH_INFO" => "/account" }
|
17
|
+
|
18
|
+
status, headers, resp = Goofy.call(env)
|
19
|
+
|
20
|
+
assert_equal "/login", headers["Location"]
|
21
|
+
assert_equal 307, status
|
22
|
+
assert_response resp, []
|
23
|
+
end
|
data/test/safe.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
require "goofy/safe"
|
3
|
+
|
4
|
+
scope do
|
5
|
+
test "secure headers" do
|
6
|
+
Goofy.plugin(Goofy::Safe)
|
7
|
+
|
8
|
+
class Hello < Goofy
|
9
|
+
define do
|
10
|
+
on root do
|
11
|
+
res.write("hello")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Goofy.define do
|
17
|
+
on root do
|
18
|
+
res.write("home")
|
19
|
+
end
|
20
|
+
|
21
|
+
on "hello" do
|
22
|
+
run(Hello)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
secure_headers = Goofy::Safe::SecureHeaders::HEADERS
|
27
|
+
|
28
|
+
_, headers, _ = Goofy.call("PATH_INFO" => "/", "SCRIPT_NAME" => "/")
|
29
|
+
secure_headers.each do |header, value|
|
30
|
+
assert_equal(value, headers[header])
|
31
|
+
end
|
32
|
+
|
33
|
+
_, headers, _ = Goofy.call("PATH_INFO" => "/hello", "SCRIPT_NAME" => "/")
|
34
|
+
secure_headers.each do |header, value|
|
35
|
+
assert_equal(value, headers[header])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
test "secure headers only in sub app" do
|
40
|
+
Goofy.settings[:default_headers] = {}
|
41
|
+
|
42
|
+
class About < Goofy
|
43
|
+
plugin(Goofy::Safe)
|
44
|
+
|
45
|
+
define do
|
46
|
+
on root do
|
47
|
+
res.write("about")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
Goofy.define do
|
53
|
+
on root do
|
54
|
+
res.write("home")
|
55
|
+
end
|
56
|
+
|
57
|
+
on "about" do
|
58
|
+
run(About)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
secure_headers = Goofy::Safe::SecureHeaders::HEADERS
|
63
|
+
|
64
|
+
_, headers, _ = Goofy.call("PATH_INFO" => "/", "SCRIPT_NAME" => "/")
|
65
|
+
secure_headers.each do |header, _|
|
66
|
+
assert(!headers.key?(header))
|
67
|
+
end
|
68
|
+
|
69
|
+
_, headers, _ = Goofy.call("PATH_INFO" => "/about", "SCRIPT_NAME" => "/")
|
70
|
+
secure_headers.each do |header, value|
|
71
|
+
assert_equal(value, headers[header])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/test/segment.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
setup do
|
4
|
+
Goofy.define do
|
5
|
+
on "post" do
|
6
|
+
on :id do |id|
|
7
|
+
res.write id
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
{ "SCRIPT_NAME" => "/", "PATH_INFO" => "/post" }
|
13
|
+
end
|
14
|
+
|
15
|
+
test "matches numeric ids" do |env|
|
16
|
+
env["PATH_INFO"] += "/1"
|
17
|
+
|
18
|
+
_, _, resp = Goofy.call(env)
|
19
|
+
|
20
|
+
assert_response resp, ["1"]
|
21
|
+
end
|
22
|
+
|
23
|
+
test "matches decimal numbers" do |env|
|
24
|
+
env["PATH_INFO"] += "/1.1"
|
25
|
+
|
26
|
+
_, _, resp = Goofy.call(env)
|
27
|
+
|
28
|
+
assert_response resp, ["1.1"]
|
29
|
+
end
|
30
|
+
|
31
|
+
test "matches slugs" do |env|
|
32
|
+
env["PATH_INFO"] += "/my-blog-post-about-Goofy"
|
33
|
+
|
34
|
+
_, _, resp = Goofy.call(env)
|
35
|
+
|
36
|
+
assert_response resp, ["my-blog-post-about-Goofy"]
|
37
|
+
end
|
38
|
+
|
39
|
+
test "matches only the first segment available" do |env|
|
40
|
+
env["PATH_INFO"] += "/one/two/three"
|
41
|
+
|
42
|
+
_, _, resp = Goofy.call(env)
|
43
|
+
|
44
|
+
assert_response resp, ["one"]
|
45
|
+
end
|