lotus-controller 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +7 -14
- data/.travis.yml +5 -0
- data/.yardopts +4 -0
- data/Gemfile +13 -2
- data/README.md +627 -6
- data/Rakefile +17 -1
- data/lib/lotus-controller.rb +1 -0
- data/lib/lotus/action.rb +55 -0
- data/lib/lotus/action/callable.rb +93 -0
- data/lib/lotus/action/callbacks.rb +138 -0
- data/lib/lotus/action/cookie_jar.rb +97 -0
- data/lib/lotus/action/cookies.rb +60 -0
- data/lib/lotus/action/exposable.rb +81 -0
- data/lib/lotus/action/mime.rb +190 -0
- data/lib/lotus/action/params.rb +53 -0
- data/lib/lotus/action/rack.rb +97 -0
- data/lib/lotus/action/redirect.rb +34 -0
- data/lib/lotus/action/session.rb +48 -0
- data/lib/lotus/action/throwable.rb +130 -0
- data/lib/lotus/controller.rb +65 -2
- data/lib/lotus/controller/dsl.rb +54 -0
- data/lib/lotus/controller/version.rb +4 -1
- data/lib/lotus/http/status.rb +103 -0
- data/lib/rack-patch.rb +20 -0
- data/lotus-controller.gemspec +16 -11
- data/test/action/callbacks_test.rb +99 -0
- data/test/action/params_test.rb +29 -0
- data/test/action_test.rb +31 -0
- data/test/controller_test.rb +24 -0
- data/test/cookies_test.rb +36 -0
- data/test/fixtures.rb +501 -0
- data/test/integration/mime_type_test.rb +175 -0
- data/test/integration/routing_test.rb +141 -0
- data/test/integration/sessions_test.rb +63 -0
- data/test/redirect_test.rb +20 -0
- data/test/session_test.rb +19 -0
- data/test/test_helper.rb +24 -0
- data/test/throw_test.rb +93 -0
- data/test/version_test.rb +7 -0
- metadata +112 -9
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'lotus/router'
|
3
|
+
|
4
|
+
MimeRoutes = Lotus::Router.new do
|
5
|
+
get '/', to: 'mimes#default'
|
6
|
+
get '/custom', to: 'mimes#custom'
|
7
|
+
get '/accept', to: 'mimes#accept'
|
8
|
+
get '/restricted', to: 'mimes#restricted'
|
9
|
+
end
|
10
|
+
|
11
|
+
class MimesController
|
12
|
+
include Lotus::Controller
|
13
|
+
|
14
|
+
action 'Default' do
|
15
|
+
def call(params)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
action 'Custom' do
|
20
|
+
def call(params)
|
21
|
+
self.content_type = 'application/xml'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
action 'Accept' do
|
26
|
+
def call(params)
|
27
|
+
self.headers.merge!({'X-AcceptDefault' => accept?('application/octet-stream').to_s })
|
28
|
+
self.headers.merge!({'X-AcceptHtml' => accept?('text/html').to_s })
|
29
|
+
self.headers.merge!({'X-AcceptXml' => accept?('application/xml').to_s })
|
30
|
+
self.headers.merge!({'X-AcceptJson' => accept?('text/json').to_s })
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
action 'Restricted' do
|
35
|
+
accept :html, :json
|
36
|
+
|
37
|
+
def call(params)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'Content type' do
|
43
|
+
before do
|
44
|
+
@app = Rack::MockRequest.new(MimeRoutes)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'fallbacks to the default "Content-Type" header when the request is lacking of this information' do
|
48
|
+
response = @app.get('/')
|
49
|
+
response.headers['Content-Type'].must_equal 'application/octet-stream'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns the specified "Content-Type" header' do
|
53
|
+
response = @app.get('/custom')
|
54
|
+
response.headers['Content-Type'].must_equal 'application/xml'
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'when Accept is sent' do
|
58
|
+
it 'sets "Content-Type" header according to "Accept"' do
|
59
|
+
response = @app.get('/', 'HTTP_ACCEPT' => '*/*')
|
60
|
+
response.headers['Content-Type'].must_equal 'application/octet-stream'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'sets "Content-Type" header according to "Accept"' do
|
64
|
+
response = @app.get('/', 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
|
65
|
+
response.headers['Content-Type'].must_equal 'text/html'
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'sets "Content-Type" header according to "Accept" quality scale' do
|
69
|
+
response = @app.get('/', 'HTTP_ACCEPT' => 'application/json;q=0.6,application/xml;q=0.9,*/*;q=0.8')
|
70
|
+
response.headers['Content-Type'].must_equal 'application/xml'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'Accept' do
|
76
|
+
before do
|
77
|
+
@app = Rack::MockRequest.new(MimeRoutes)
|
78
|
+
@response = @app.get('/accept', 'HTTP_ACCEPT' => accept)
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'when Accept is missing' do
|
82
|
+
let(:accept) { nil }
|
83
|
+
|
84
|
+
it 'accepts all' do
|
85
|
+
@response.headers['X-AcceptDefault'].must_equal 'true'
|
86
|
+
@response.headers['X-AcceptHtml'].must_equal 'true'
|
87
|
+
@response.headers['X-AcceptXml'].must_equal 'true'
|
88
|
+
@response.headers['X-AcceptJson'].must_equal 'true'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'when Accept is sent' do
|
93
|
+
describe 'when "*/*"' do
|
94
|
+
let(:accept) { '*/*' }
|
95
|
+
|
96
|
+
it 'accepts all' do
|
97
|
+
@response.headers['X-AcceptDefault'].must_equal 'true'
|
98
|
+
@response.headers['X-AcceptHtml'].must_equal 'true'
|
99
|
+
@response.headers['X-AcceptXml'].must_equal 'true'
|
100
|
+
@response.headers['X-AcceptJson'].must_equal 'true'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'when "text/html"' do
|
105
|
+
let(:accept) { 'text/html' }
|
106
|
+
|
107
|
+
it 'accepts selected mime types' do
|
108
|
+
@response.headers['X-AcceptDefault'].must_equal 'false'
|
109
|
+
@response.headers['X-AcceptHtml'].must_equal 'true'
|
110
|
+
@response.headers['X-AcceptXml'].must_equal 'false'
|
111
|
+
@response.headers['X-AcceptJson'].must_equal 'false'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'when weighted' do
|
116
|
+
let(:accept) { 'text/html,application/xhtml+xml,application/xml;q=0.9' }
|
117
|
+
|
118
|
+
it 'accepts selected mime types' do
|
119
|
+
@response.headers['X-AcceptDefault'].must_equal 'false'
|
120
|
+
@response.headers['X-AcceptHtml'].must_equal 'true'
|
121
|
+
@response.headers['X-AcceptXml'].must_equal 'true'
|
122
|
+
@response.headers['X-AcceptJson'].must_equal 'false'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'Restricted Accept' do
|
129
|
+
before do
|
130
|
+
@app = Rack::MockRequest.new(MimeRoutes)
|
131
|
+
@response = @app.get('/restricted', 'HTTP_ACCEPT' => accept)
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'when Accept is missing' do
|
135
|
+
let(:accept) { nil }
|
136
|
+
|
137
|
+
it 'returns the mime type according to the application defined policy' do
|
138
|
+
@response.status.must_equal 200
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'when Accept is sent' do
|
143
|
+
describe 'when "*/*"' do
|
144
|
+
let(:accept) { '*/*' }
|
145
|
+
|
146
|
+
it 'returns the mime type according to the application defined policy' do
|
147
|
+
@response.status.must_equal 200
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe 'when accepted' do
|
152
|
+
let(:accept) { 'text/html' }
|
153
|
+
|
154
|
+
it 'accepts selected mime types' do
|
155
|
+
@response.status.must_equal 200
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe 'when not accepted' do
|
160
|
+
let(:accept) { 'application/xml' }
|
161
|
+
|
162
|
+
it 'accepts selected mime types' do
|
163
|
+
@response.status.must_equal 406
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe 'when weighted' do
|
168
|
+
let(:accept) { 'text/html,application/xhtml+xml,application/xml;q=0.9' }
|
169
|
+
|
170
|
+
it 'accepts selected mime types' do
|
171
|
+
@response.status.must_equal 200
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'lotus/router'
|
3
|
+
|
4
|
+
Routes = Lotus::Router.new do
|
5
|
+
get '/', to: 'root'
|
6
|
+
get '/team', to: 'about#team'
|
7
|
+
get '/contacts', to: 'about#contacts'
|
8
|
+
|
9
|
+
resource :identity
|
10
|
+
resources :flowers
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'Lotus::Router integration' do
|
14
|
+
before do
|
15
|
+
@app = Rack::MockRequest.new(Routes)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'calls simple action' do
|
19
|
+
response = @app.get('/')
|
20
|
+
|
21
|
+
response.status.must_equal 200
|
22
|
+
response.body.must_equal '{}'
|
23
|
+
response.headers['X-Test'].must_equal 'test'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "calls a controller's class action" do
|
27
|
+
response = @app.get('/team')
|
28
|
+
|
29
|
+
response.status.must_equal 200
|
30
|
+
response.body.must_equal '{}'
|
31
|
+
response.headers['X-Test'].must_equal 'test'
|
32
|
+
end
|
33
|
+
|
34
|
+
it "calls a controller's action (with DSL)" do
|
35
|
+
response = @app.get('/contacts')
|
36
|
+
|
37
|
+
response.status.must_equal 200
|
38
|
+
response.body.must_equal '{}'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns a 404 for unknown path' do
|
42
|
+
response = @app.get('/unknown')
|
43
|
+
|
44
|
+
response.status.must_equal 404
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'resource' do
|
48
|
+
it 'calls GET show' do
|
49
|
+
response = @app.get('/identity')
|
50
|
+
|
51
|
+
response.status.must_equal 200
|
52
|
+
response.body.must_equal "{}"
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'calls GET new' do
|
56
|
+
response = @app.get('/identity/new')
|
57
|
+
|
58
|
+
response.status.must_equal 200
|
59
|
+
response.body.must_equal '{}'
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'calls POST create' do
|
63
|
+
response = @app.post('/identity', params: { identity: { avatar: { image: 'jodosha.png' } }})
|
64
|
+
|
65
|
+
response.status.must_equal 200
|
66
|
+
response.body.must_equal "{:identity=>{:avatar=>{:image=>\"jodosha.png\"}}}"
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'calls GET edit' do
|
70
|
+
response = @app.get('/identity/edit')
|
71
|
+
|
72
|
+
response.status.must_equal 200
|
73
|
+
response.body.must_equal "{}"
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'calls PATCH update' do
|
77
|
+
response = @app.request('PATCH', '/identity', params: { identity: { avatar: { image: 'jodosha-2x.png' } }})
|
78
|
+
|
79
|
+
response.status.must_equal 200
|
80
|
+
response.body.must_equal "{:identity=>{:avatar=>{:image=>\"jodosha-2x.png\"}}}"
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'calls DELETE destroy' do
|
84
|
+
response = @app.delete('/identity')
|
85
|
+
|
86
|
+
response.status.must_equal 200
|
87
|
+
response.body.must_equal "{}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'resources' do
|
92
|
+
it 'calls GET index' do
|
93
|
+
response = @app.get('/flowers')
|
94
|
+
|
95
|
+
response.status.must_equal 200
|
96
|
+
response.body.must_equal '{}'
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'calls GET show' do
|
100
|
+
response = @app.get('/flowers/23')
|
101
|
+
|
102
|
+
response.status.must_equal 200
|
103
|
+
response.body.must_equal "{:id=>\"23\"}"
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'calls GET new' do
|
107
|
+
response = @app.get('/flowers/new')
|
108
|
+
|
109
|
+
response.status.must_equal 200
|
110
|
+
response.body.must_equal '{}'
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'calls POST create' do
|
114
|
+
response = @app.post('/flowers', params: { flower: { name: 'Lotus' } })
|
115
|
+
|
116
|
+
response.status.must_equal 200
|
117
|
+
response.body.must_equal "{:flower=>{:name=>\"Lotus\"}}"
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'calls GET edit' do
|
121
|
+
response = @app.get('/flowers/23/edit')
|
122
|
+
|
123
|
+
response.status.must_equal 200
|
124
|
+
response.body.must_equal "{:id=>\"23\"}"
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'calls PATCH update' do
|
128
|
+
response = @app.request('PATCH', '/flowers/23', params: { flower: { name: 'Lotus!' } })
|
129
|
+
|
130
|
+
response.status.must_equal 200
|
131
|
+
response.body.must_equal "{:flower=>{:name=>\"Lotus!\"}, :id=>\"23\"}"
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'calls DELETE destroy' do
|
135
|
+
response = @app.delete('/flowers/23')
|
136
|
+
|
137
|
+
response.status.must_equal 200
|
138
|
+
response.body.must_equal "{:id=>\"23\"}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'lotus/router'
|
4
|
+
|
5
|
+
SessionRoutes = Lotus::Router.new do
|
6
|
+
get '/', to: 'dashboard#index'
|
7
|
+
post '/login', to: 'sessions#create'
|
8
|
+
delete '/logout', to: 'sessions#destroy'
|
9
|
+
end
|
10
|
+
|
11
|
+
SessionApplication = Rack::Builder.new do
|
12
|
+
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
|
13
|
+
run SessionRoutes
|
14
|
+
end.to_app
|
15
|
+
|
16
|
+
StandaloneSessionApplication = Rack::Builder.new do
|
17
|
+
use Rack::Session::Cookie, secret: SecureRandom.hex(16)
|
18
|
+
run StandaloneSession.new
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'Sessions' do
|
22
|
+
include Rack::Test::Methods
|
23
|
+
|
24
|
+
def app
|
25
|
+
SessionApplication
|
26
|
+
end
|
27
|
+
|
28
|
+
it "denies access if user isn't loggedin" do
|
29
|
+
get '/'
|
30
|
+
last_response.status.must_equal 401
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'grant access after login' do
|
34
|
+
post '/login'
|
35
|
+
follow_redirect!
|
36
|
+
last_response.status.must_equal 200
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'logs out' do
|
40
|
+
post '/login'
|
41
|
+
follow_redirect!
|
42
|
+
last_response.status.must_equal 200
|
43
|
+
|
44
|
+
delete '/logout'
|
45
|
+
|
46
|
+
get '/'
|
47
|
+
last_response.status.must_equal 401
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'Standalone Sessions' do
|
52
|
+
include Rack::Test::Methods
|
53
|
+
|
54
|
+
def app
|
55
|
+
StandaloneSessionApplication
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'sets the session value' do
|
59
|
+
get '/'
|
60
|
+
last_response.status.must_equal 200
|
61
|
+
last_response.headers.fetch('Set-Cookie').must_match(/\Arack\.session/)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Lotus::Action do
|
4
|
+
describe 'redirect' do
|
5
|
+
it 'redirects to the given path' do
|
6
|
+
action = RedirectAction.new
|
7
|
+
response = action.call({})
|
8
|
+
|
9
|
+
response[0].must_equal(302)
|
10
|
+
response[1].must_equal({ 'Content-Type' => 'application/octet-stream', 'Location' => '/destination' })
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'redirects with custom status code' do
|
14
|
+
action = StatusRedirectAction.new
|
15
|
+
response = action.call({})
|
16
|
+
|
17
|
+
response[0].must_equal(301)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Lotus::Action do
|
4
|
+
describe 'session' do
|
5
|
+
it 'captures session from Rack env' do
|
6
|
+
action = SessionAction.new
|
7
|
+
action.call({'rack.session' => session = { 'user_id' => '23' }})
|
8
|
+
|
9
|
+
action.send(:session).must_equal(session)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns empty hash when it is missing' do
|
13
|
+
action = SessionAction.new
|
14
|
+
action.call({})
|
15
|
+
|
16
|
+
action.send(:session).must_equal({})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
if ENV['COVERAGE'] == 'true'
|
5
|
+
require 'simplecov'
|
6
|
+
require 'coveralls'
|
7
|
+
|
8
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
9
|
+
SimpleCov::Formatter::HTMLFormatter,
|
10
|
+
Coveralls::SimpleCov::Formatter
|
11
|
+
]
|
12
|
+
|
13
|
+
SimpleCov.start do
|
14
|
+
command_name 'test'
|
15
|
+
add_filter 'test'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'minitest/autorun'
|
20
|
+
$:.unshift 'lib'
|
21
|
+
require 'lotus/controller'
|
22
|
+
require 'lotus/action/cookies'
|
23
|
+
require 'lotus/action/session'
|
24
|
+
require 'fixtures'
|