rasti-web 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestController < Rasti::Web::Controller
4
+ CustomError = Class.new StandardError
5
+
6
+ def test
7
+ render.html 'Test HTML'
8
+ end
9
+
10
+ def exception
11
+ raise 'Unexpected error'
12
+ end
13
+
14
+ def fail
15
+ raise CustomError, 'Expected error'
16
+ end
17
+
18
+ rescue_from CustomError do |ex|
19
+ render.status 500, ex.message
20
+ end
21
+ end
22
+
23
+ describe Rasti::Web::Controller do
24
+
25
+ it 'Action endpoint' do
26
+ action = TestController.action :test
27
+ env = Rack::MockRequest.env_for '/test'
28
+ status, headers, response = action.call env
29
+
30
+ action.must_be_instance_of Rasti::Web::Endpoint
31
+ status.must_equal 200
32
+ headers['Content-Type'].must_equal 'text/html'
33
+ response.body.must_equal ['Test HTML']
34
+ end
35
+
36
+ it 'Invalid action' do
37
+ error = proc { TestController.action :invalid }.must_raise RuntimeError
38
+ error.message.must_equal "Undefined action 'invalid' in TestController"
39
+ end
40
+
41
+ it 'Rescue exception' do
42
+ action = TestController.action :fail
43
+ env = Rack::MockRequest.env_for '/fail'
44
+ status, headers, response = action.call env
45
+
46
+ status.must_equal 500
47
+ response.body.must_equal ['Expected error']
48
+ end
49
+
50
+ it 'Unexpected exception' do
51
+ action = TestController.action :exception
52
+ env = Rack::MockRequest.env_for '/exception'
53
+
54
+ error = proc { action.call env }.must_raise RuntimeError
55
+ error.message.must_equal 'Unexpected error'
56
+ end
57
+
58
+ end
@@ -0,0 +1,8 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+ SimpleCov.start
@@ -0,0 +1,22 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Web::Endpoint do
4
+
5
+ it 'Call' do
6
+ endpoint = Rasti::Web::Endpoint.new do |req, res, render|
7
+ req.must_be_instance_of Rasti::Web::Request
8
+ res.must_be_instance_of Rack::Response
9
+
10
+ render.text 'Content'
11
+ end
12
+
13
+ env = Rack::MockRequest.env_for '/'
14
+
15
+ status, headers, response = endpoint.call env
16
+
17
+ status.must_equal 200
18
+ headers['Content-Type'].must_equal 'text/plain'
19
+ response.body.must_equal ['Content']
20
+ end
21
+
22
+ end
@@ -0,0 +1,22 @@
1
+ require 'coverage_helper'
2
+ require 'rasti-web'
3
+ require 'minitest/autorun'
4
+ require 'turn'
5
+ require 'rack/test'
6
+
7
+ Turn.config do |c|
8
+ c.format = :pretty
9
+ c.natural = true
10
+ c.ansi = true
11
+ end
12
+
13
+ module ContextMethodHelper
14
+ def page_header(text)
15
+ "<h1>#{text}</h1>"
16
+ end
17
+ end
18
+
19
+ Rasti::Web.configure do |config|
20
+ config.views_path = File.expand_path '../views', __FILE__
21
+ config.helpers << ContextMethodHelper
22
+ end
@@ -0,0 +1,263 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Web::Render do
4
+
5
+ let(:request) { Rack::Request.new Hash.new }
6
+ let(:response) { Rack::Response.new }
7
+ let(:render) { Rasti::Web::Render.new request, response }
8
+
9
+ describe 'Status' do
10
+
11
+ it 'Code' do
12
+ render.status 404
13
+
14
+ response.status.must_equal 404
15
+ response['Content-Type'].must_be_nil
16
+ response.body.must_equal []
17
+ end
18
+
19
+ it 'Code and body' do
20
+ render.status 500, 'Internal server error'
21
+
22
+ response.status.must_equal 500
23
+ response['Content-Type'].must_be_nil
24
+ response.body.must_equal ['Internal server error']
25
+ end
26
+
27
+ it 'Code and headers' do
28
+ render.status 201, 'Content-Type' => 'application/json'
29
+
30
+ response.status.must_equal 201
31
+ response['Content-Type'].must_equal 'application/json'
32
+ response.body.must_equal []
33
+ end
34
+
35
+ it 'Code, body and headers' do
36
+ render.status 403, 'Forbidden', 'Content-Type' => 'text/html'
37
+
38
+ response.status.must_equal 403
39
+ response['Content-Type'].must_equal 'text/html'
40
+ response.body.must_equal ['Forbidden']
41
+ end
42
+
43
+ end
44
+
45
+ describe 'Text' do
46
+
47
+ it 'Body' do
48
+ render.text 'Plain text'
49
+
50
+ response.status.must_equal 200
51
+ response['Content-Type'].must_equal 'text/plain'
52
+ response.body.must_equal ['Plain text']
53
+ end
54
+
55
+ it 'Body and status' do
56
+ render.text 'Internal server error', 500
57
+
58
+ response.status.must_equal 500
59
+ response['Content-Type'].must_equal 'text/plain'
60
+ response.body.must_equal ['Internal server error']
61
+ end
62
+
63
+ it 'Body and headers' do
64
+ render.text 'Encoded text', 'Content-Encoding' => 'gzip'
65
+
66
+ response.status.must_equal 200
67
+ response['Content-Type'].must_equal 'text/plain'
68
+ response['Content-Encoding'].must_equal 'gzip'
69
+ response.body.must_equal ['Encoded text']
70
+ end
71
+
72
+ it 'Body, status and headers' do
73
+ render.text 'Not found', 404, 'Content-Encoding' => 'gzip'
74
+
75
+ response.status.must_equal 404
76
+ response['Content-Type'].must_equal 'text/plain'
77
+ response['Content-Encoding'].must_equal 'gzip'
78
+ response.body.must_equal ['Not found']
79
+ end
80
+
81
+ end
82
+
83
+ describe 'HTML' do
84
+
85
+ it 'Body' do
86
+ render.html '<h1>Title</h1>'
87
+
88
+ response.status.must_equal 200
89
+ response['Content-Type'].must_equal 'text/html'
90
+ response.body.must_equal ['<h1>Title</h1>']
91
+ end
92
+
93
+ it 'Body and status' do
94
+ render.html '<h1>Internal server error</h1>', 500
95
+
96
+ response.status.must_equal 500
97
+ response['Content-Type'].must_equal 'text/html'
98
+ response.body.must_equal ['<h1>Internal server error</h1>']
99
+ end
100
+
101
+ it 'Body and headers' do
102
+ render.html '<p>Encoded text</p>', 'Content-Encoding' => 'gzip'
103
+
104
+ response.status.must_equal 200
105
+ response['Content-Type'].must_equal 'text/html'
106
+ response['Content-Encoding'].must_equal 'gzip'
107
+ response.body.must_equal ['<p>Encoded text</p>']
108
+ end
109
+
110
+ it 'Body, status and headers' do
111
+ render.html '<h1>Not found</h1>', 404, 'Content-Encoding' => 'gzip'
112
+
113
+ response.status.must_equal 404
114
+ response['Content-Type'].must_equal 'text/html'
115
+ response['Content-Encoding'].must_equal 'gzip'
116
+ response.body.must_equal ['<h1>Not found</h1>']
117
+ end
118
+
119
+ end
120
+
121
+ describe 'Json' do
122
+
123
+ let(:object) { {id: 123, color: 'red'} }
124
+
125
+ it 'Body' do
126
+ render.json object
127
+
128
+ response.status.must_equal 200
129
+ response['Content-Type'].must_equal 'application/json'
130
+ response.body.must_equal [object.to_json]
131
+ end
132
+
133
+ it 'Body string' do
134
+ render.json '{"x":1,"y":2}'
135
+
136
+ response.status.must_equal 200
137
+ response['Content-Type'].must_equal 'application/json'
138
+ response.body.must_equal ['{"x":1,"y":2}']
139
+ end
140
+
141
+ it 'Body and status' do
142
+ render.json object, 422
143
+
144
+ response.status.must_equal 422
145
+ response['Content-Type'].must_equal 'application/json'
146
+ response.body.must_equal [object.to_json]
147
+ end
148
+
149
+ it 'Body and headers' do
150
+ render.json object, 'Content-Encoding' => 'gzip'
151
+
152
+ response.status.must_equal 200
153
+ response['Content-Type'].must_equal 'application/json'
154
+ response['Content-Encoding'].must_equal 'gzip'
155
+ response.body.must_equal [object.to_json]
156
+ end
157
+
158
+ it 'Body, status and headers' do
159
+ render.json object, 422, 'Content-Encoding' => 'gzip'
160
+
161
+ response.status.must_equal 422
162
+ response['Content-Type'].must_equal 'application/json'
163
+ response['Content-Encoding'].must_equal 'gzip'
164
+ response.body.must_equal [object.to_json]
165
+ end
166
+
167
+ end
168
+
169
+ describe 'Javascript' do
170
+
171
+ it 'Body' do
172
+ render.js 'alert("hello");'
173
+
174
+ response.status.must_equal 200
175
+ response['Content-Type'].must_equal 'application/javascript'
176
+ response.body.must_equal ['alert("hello");']
177
+ end
178
+
179
+ it 'Body and status' do
180
+ render.js 'alert("hello");', 206
181
+
182
+ response.status.must_equal 206
183
+ response['Content-Type'].must_equal 'application/javascript'
184
+ response.body.must_equal ['alert("hello");']
185
+ end
186
+
187
+ it 'Body and headers' do
188
+ render.js 'alert("hello");', 'Content-Encoding' => 'gzip'
189
+
190
+ response.status.must_equal 200
191
+ response['Content-Type'].must_equal 'application/javascript'
192
+ response['Content-Encoding'].must_equal 'gzip'
193
+ response.body.must_equal ['alert("hello");']
194
+ end
195
+
196
+ it 'Body, status and headers' do
197
+ render.js 'alert("hello");', 206, 'Content-Encoding' => 'gzip'
198
+
199
+ response.status.must_equal 206
200
+ response['Content-Type'].must_equal 'application/javascript'
201
+ response['Content-Encoding'].must_equal 'gzip'
202
+ response.body.must_equal ['alert("hello");']
203
+ end
204
+
205
+ end
206
+
207
+ it 'Partial' do
208
+ render.partial 'context_and_locals', title: 'Welcome', text: 'Hello world'
209
+
210
+ response.status.must_equal 200
211
+ response['Content-Type'].must_equal 'text/html'
212
+ response.body.must_equal ['<h1>Welcome</h1><div>Hello world</div>']
213
+ end
214
+
215
+ describe 'Layout' do
216
+
217
+ it 'Default' do
218
+ render.layout { 'Page content' }
219
+
220
+ response.status.must_equal 200
221
+ response['Content-Type'].must_equal 'text/html'
222
+ response.body.must_equal ['<html><body>Page content</body></html>']
223
+ end
224
+
225
+ it 'Custom' do
226
+ render.layout('custom_layout') { 'Page content' }
227
+
228
+ response.status.must_equal 200
229
+ response['Content-Type'].must_equal 'text/html'
230
+ response.body.must_equal ['<html><body class="custom">Page content</body></html>']
231
+ end
232
+
233
+ it 'Empty' do
234
+ render.layout
235
+
236
+ response.status.must_equal 200
237
+ response['Content-Type'].must_equal 'text/html'
238
+ response.body.must_equal ['<html><body></body></html>']
239
+ end
240
+
241
+ end
242
+
243
+ describe 'View' do
244
+
245
+ it 'Default layout' do
246
+ render.view 'context_and_locals', title: 'Welcome', text: 'Hello world'
247
+
248
+ response.status.must_equal 200
249
+ response['Content-Type'].must_equal 'text/html'
250
+ response.body.must_equal ['<html><body><h1>Welcome</h1><div>Hello world</div></body></html>']
251
+ end
252
+
253
+ it 'Custom layout' do
254
+ render.view 'context_and_locals', {title: 'Welcome', text: 'Hello world'}, 'custom_layout'
255
+
256
+ response.status.must_equal 200
257
+ response['Content-Type'].must_equal 'text/html'
258
+ response.body.must_equal ['<html><body class="custom"><h1>Welcome</h1><div>Hello world</div></body></html>']
259
+ end
260
+
261
+ end
262
+
263
+ end
@@ -0,0 +1,35 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Web::Request do
4
+
5
+ it 'Route params' do
6
+ env = Rack::MockRequest.env_for '/'
7
+ env[Rasti::Web::ROUTE_PARAMS] = {'x' => 1, 'y' => 2}
8
+
9
+ request = Rasti::Web::Request.new env
10
+
11
+ request['x'].must_equal 1
12
+ request['y'].must_equal 2
13
+ end
14
+
15
+ it 'Json body params' do
16
+ env = Rack::MockRequest.env_for '/', input: '{"lat": 10, "lon": 20}', 'CONTENT_TYPE' => 'application/json'
17
+
18
+ request = Rasti::Web::Request.new env
19
+
20
+ request.must_be :json?
21
+ request['lat'].must_equal 10
22
+ request['lon'].must_equal 20
23
+ end
24
+
25
+ it 'No json body params' do
26
+ env = Rack::MockRequest.env_for '/', input: '{"lat": 10, "lon": 20}', 'CONTENT_TYPE' => 'other/type'
27
+
28
+ request = Rasti::Web::Request.new env
29
+
30
+ request.wont_be :json?
31
+ request['lat'].must_be_nil
32
+ request['lon'].must_be_nil
33
+ end
34
+
35
+ end
@@ -0,0 +1,67 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Web::Route do
4
+
5
+ def build_route(pattern, endpoint=:fake_endpoint)
6
+ Rasti::Web::Route.new pattern, :fake_endpoint
7
+ end
8
+
9
+ it 'Static' do
10
+ route = build_route '/resource'
11
+
12
+ route.pattern.must_equal '/resource'
13
+ route.regexp.to_s.must_equal /^\/resource$/.to_s
14
+ route.endpoint.must_equal :fake_endpoint
15
+ route.params.must_equal []
16
+ end
17
+
18
+ it 'Params' do
19
+ route = build_route '/resource/:id/:action'
20
+
21
+ route.pattern.must_equal '/resource/:id/:action'
22
+ route.regexp.to_s.must_equal /^\/resource\/([^\/?#]+)\/([^\/?#]+)$/.to_s
23
+ route.endpoint.must_equal :fake_endpoint
24
+ route.params.must_equal %w(id action)
25
+ end
26
+
27
+ it 'Match' do
28
+ build_route('/').must_be :match?, '/'
29
+ build_route('/').wont_be :match?, '/resource'
30
+
31
+ build_route('/resource').must_be :match?, '/resource'
32
+ build_route('/resource').must_be :match?, '/resource/'
33
+ build_route('/resource').wont_be :match?, '/'
34
+
35
+ build_route('/resource/:id/:action').must_be :match?, '/resource/123/show'
36
+ build_route('/resource/:id/:action').wont_be :match?, '/123/show'
37
+ end
38
+
39
+ it 'Extract params' do
40
+ build_route('/resource').extract_params('/resource').must_equal Hash.new
41
+ build_route('/resource/:id/:action').extract_params('/resource/123/show').must_equal 'id' => '123', 'action' => 'show'
42
+ end
43
+
44
+ it 'Normalize path' do
45
+ build_route('').pattern.must_equal '/'
46
+ build_route('/').pattern.must_equal '/'
47
+ build_route('/resource').pattern.must_equal '/resource'
48
+ build_route('/resource/').pattern.must_equal '/resource'
49
+ end
50
+
51
+ it 'Block endpoint' do
52
+ route = Rasti::Web::Route.new('/') do |req, res|
53
+ res.status = 404
54
+ res['Content-Type'] = 'text/html'
55
+ res.write '<h1>Not found</h1>'
56
+ end
57
+
58
+ route.endpoint.must_be_instance_of Rasti::Web::Endpoint
59
+
60
+ status, headers, response = route.endpoint.call(:fake_env)
61
+
62
+ status.must_equal 404
63
+ headers['Content-Type'].must_equal 'text/html'
64
+ response.body.must_equal ['<h1>Not found</h1>']
65
+ end
66
+
67
+ end
@@ -0,0 +1,38 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Web::Router do
4
+
5
+ let(:router) { Rasti::Web::Router.new }
6
+
7
+ def get(path)
8
+ Rack::MockRequest.env_for path, method: :get
9
+ end
10
+
11
+ def post(path)
12
+ Rack::MockRequest.env_for path, method: :post
13
+ end
14
+
15
+ it 'Verbs' do
16
+ %w(delete get head options patch post put).each do |verb|
17
+ router.must_respond_to verb
18
+ end
19
+ end
20
+
21
+ it 'Route matching' do
22
+ router.get '/resources', ->(env) { :index }
23
+ router.post '/resources/:id', ->(env) { :update }
24
+ router.not_found ->(env) { :not_found }
25
+
26
+ router.call(get('/resources')).must_equal :index
27
+ router.call(post('/resources/123')).must_equal :update
28
+ router.call(post('/resources')).must_equal :not_found
29
+ end
30
+
31
+ it 'Not found' do
32
+ status, headers, response = router.call get('/not_found')
33
+
34
+ status.must_equal 404
35
+ response.body.must_equal ['Not found: GET /not_found']
36
+ end
37
+
38
+ end
@@ -0,0 +1,29 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Web::Template do
4
+
5
+ class Context
6
+ include ContextMethodHelper
7
+ end
8
+
9
+ it 'Plain HTML' do
10
+ Rasti::Web::Template.render('plain_html').must_equal '<div>Hello world</div>'
11
+ end
12
+
13
+ it 'Context method' do
14
+ Rasti::Web::Template.render('context_method', Context.new).must_equal '<h1>Hello world</h1>'
15
+ end
16
+
17
+ it 'Local variable' do
18
+ Rasti::Web::Template.render('local_variable', nil, text: 'Welcome').must_equal '<h1>Welcome</h1>'
19
+ end
20
+
21
+ it 'Invalid template' do
22
+ proc { Rasti::Web::Template.render 'invalid' }.must_raise RuntimeError
23
+ end
24
+
25
+ it 'Nested' do
26
+ Rasti::Web::Template.render('layout') { 'inner text' }.must_equal '<html><body>inner text</body></html>'
27
+ end
28
+
29
+ end
@@ -0,0 +1 @@
1
+ <%= page_header title %><div><%= text %></div>
@@ -0,0 +1 @@
1
+ <%= page_header 'Hello world' %>
@@ -0,0 +1 @@
1
+ <html><body class="custom"><%= yield %></body></html>
@@ -0,0 +1 @@
1
+ <html><body><%= yield %></body></html>
@@ -0,0 +1 @@
1
+ <h1><%= text %></h1>
@@ -0,0 +1 @@
1
+ <div>Hello world</div>