rasti-web 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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>