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.
@@ -0,0 +1,99 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Action do
4
+ describe '#before' do
5
+ it 'invokes the method(s) from the given symbol(s) before the action is run' do
6
+ action = BeforeMethodAction.new
7
+ action.call({})
8
+
9
+ action.article.must_equal 'Bonjour!'.reverse
10
+ end
11
+
12
+ it 'invokes the given block before the action is run' do
13
+ action = BeforeBlockAction.new
14
+ action.call({})
15
+
16
+ action.article.must_equal 'Good morning!'.reverse
17
+ end
18
+
19
+ it 'inherits callbacks from superclass' do
20
+ action = SubclassBeforeMethodAction.new
21
+ action.call({})
22
+
23
+ action.article.must_equal 'Bonjour!'.reverse.upcase
24
+ end
25
+
26
+ it 'can optionally have params in method signature' do
27
+ action = ParamsBeforeMethodAction.new
28
+ action.call(params = {bang: '!'})
29
+
30
+ action.article.must_equal 'Bonjour!!'.reverse
31
+ action.exposed_params.must_equal(params)
32
+ end
33
+
34
+ it 'yields params when the callback is a block' do
35
+ action = YieldBeforeBlockAction.new
36
+ response = action.call(params = { twentythree: '23' })
37
+
38
+ response[0].must_equal 200
39
+ action.yielded_params.must_equal params
40
+ end
41
+
42
+ describe 'on error' do
43
+ it 'stops the callbacks execution and returns an HTTP 500 status' do
44
+ action = ErrorBeforeMethodAction.new
45
+ response = action.call({})
46
+
47
+ response[0].must_equal 500
48
+ action.article.must_be_nil
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#after' do
54
+ it 'invokes the method(s) from the given symbol(s) after the action is run' do
55
+ action = AfterMethodAction.new
56
+ action.call({})
57
+
58
+ action.egg.must_equal 'gE!g'
59
+ end
60
+
61
+ it 'invokes the given block after the action is run' do
62
+ action = AfterBlockAction.new
63
+ action.call({})
64
+
65
+ action.egg.must_equal 'Coque'.reverse
66
+ end
67
+
68
+ it 'inherits callbacks from superclass' do
69
+ action = SubclassAfterMethodAction.new
70
+ action.call({})
71
+
72
+ action.egg.must_equal 'gE!g'.upcase
73
+ end
74
+
75
+ it 'can optionally have params in method signature' do
76
+ action = ParamsAfterMethodAction.new
77
+ action.call(question: '?')
78
+
79
+ action.egg.must_equal 'gE!g?'
80
+ end
81
+
82
+ it 'yields params when the callback is a block' do
83
+ action = YieldAfterBlockAction.new
84
+ action.call(params = { fortytwo: '42' })
85
+
86
+ action.meaning_of_life_params.must_equal params
87
+ end
88
+
89
+ describe 'on error' do
90
+ it 'stops the callbacks execution and returns an HTTP 500 status' do
91
+ action = ErrorAfterMethodAction.new
92
+ response = action.call({})
93
+
94
+ response[0].must_equal 500
95
+ action.egg.must_be_nil
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,29 @@
1
+ require 'test_helper'
2
+ require 'rack'
3
+
4
+ describe Lotus::Action::Params do
5
+ it 'accepts params from "router.params"' do
6
+ action = ParamsAction.new
7
+ response = action.call({ 'router.params' => {id: '23'} })
8
+
9
+ response[2].must_equal ["{:id=>\"23\"}"]
10
+ end
11
+
12
+ it 'accepts params as they are, for testing purposes' do
13
+ action = ParamsAction.new
14
+ response = action.call({id: '23'})
15
+
16
+ response[2].must_equal ["{:id=>\"23\"}"]
17
+ end
18
+
19
+ it 'accepts params from "rack.input" as request body' do
20
+ response = Rack::MockRequest.new(ParamsAction.new).request('PATCH', "?id=23", params: { x: { foo: 'bar' } })
21
+ response.body.must_match "{:id=>\"23\", :x=>{:foo=>\"bar\"}}"
22
+ end
23
+
24
+ it 'is frozen' do
25
+ params = Lotus::Action::Params.new({id: '23'})
26
+
27
+ -> { params.delete(:id) }.must_raise(RuntimeError)
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Action do
4
+ describe '#call' do
5
+ it 'calls an action' do
6
+ response = CallAction.new.call({})
7
+
8
+ response[0].must_equal 201
9
+ response[1].must_equal({'Content-Type' => 'application/octet-stream', 'X-Custom' => 'OK'})
10
+ response[2].must_equal ['Hi from TestAction!']
11
+ end
12
+
13
+ it 'returns an HTTP 500 status code when an exception is raised' do
14
+ response = ErrorCallAction.new.call({})
15
+
16
+ response[0].must_equal 500
17
+ response[2].must_equal ['Internal Server Error']
18
+ end
19
+ end
20
+
21
+ describe '#expose' do
22
+ it 'creates a getter for the given ivar' do
23
+ action = ExposeAction.new
24
+
25
+ response = action.call({})
26
+ response[0].must_equal 200
27
+
28
+ action.exposures.must_equal({ film: '400 ASA', time: nil })
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Controller do
4
+ describe '.action' do
5
+ it 'creates an action for the given name' do
6
+ action = TestController::Index.new
7
+ action.call({name: 'test'})
8
+ action.xyz.must_equal 'test'
9
+ end
10
+
11
+ it "raises an error when the given name isn't a valid Ruby identifier" do
12
+ -> {
13
+ class Controller
14
+ include Lotus::Controller
15
+
16
+ action 12 do
17
+ def call(params)
18
+ end
19
+ end
20
+ end
21
+ }.must_raise TypeError
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Action do
4
+ describe 'cookies' do
5
+ it 'gets cookies' do
6
+ action = GetCookiesAction.new
7
+ response = action.call({'HTTP_COOKIE' => 'foo=bar'})
8
+
9
+ action.send(:cookies).must_equal({foo: 'bar'})
10
+ response[1].must_equal({'Content-Type' => 'application/octet-stream', 'Set-Cookie' => 'foo=bar'})
11
+ end
12
+
13
+ it 'sets cookies' do
14
+ action = SetCookiesAction.new
15
+ response = action.call({})
16
+
17
+ response[2].must_equal(['yo'])
18
+ response[1].must_equal({'Content-Type' => 'application/octet-stream', 'Set-Cookie' => 'foo=yum%21'})
19
+ end
20
+
21
+ it 'sets cookies with options' do
22
+ tomorrow = Time.now + 60 * 60 * 24
23
+ action = SetCookiesWithOptionsAction.new
24
+ response = action.call({expires: tomorrow})
25
+
26
+ response[1].must_equal({'Content-Type' => 'application/octet-stream', 'Set-Cookie' => "kukki=yum%21; domain=lotusrb.org; path=/controller; expires=#{ tomorrow.gmtime.rfc2822 }; secure; HttpOnly"})
27
+ end
28
+
29
+ it 'removes cookies' do
30
+ action = RemoveCookiesAction.new
31
+ response = action.call({'HTTP_COOKIE' => 'foo=bar;rm=me'})
32
+
33
+ response[1].must_equal({'Content-Type' => 'application/octet-stream', 'Set-Cookie' => "foo=bar\nrm=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"})
34
+ end
35
+ end
36
+ end
data/test/fixtures.rb ADDED
@@ -0,0 +1,501 @@
1
+ HTTP_TEST_STATUSES = {
2
+ 100 => 'Continue',
3
+ 101 => 'Switching Protocols',
4
+ 102 => 'Processing (WebDAV) (RFC 2518)',
5
+ 103 => 'Checkpoint',
6
+ 122 => 'Request-URI too long',
7
+ 200 => 'OK',
8
+ 201 => 'Created',
9
+ 202 => 'Accepted',
10
+ 203 => 'Non-Authoritative Information',
11
+ 204 => 'No Content',
12
+ 205 => 'Reset Content',
13
+ 206 => 'Partial Content',
14
+ 207 => 'Multi-Status (WebDAV) (RFC 4918)',
15
+ 208 => 'Already Reported (WebDAV) (RFC 5842)',
16
+ 226 => 'IM Used (RFC 3229)',
17
+ 300 => 'Multiple Choices',
18
+ 301 => 'Moved Permanently',
19
+ 302 => 'Found',
20
+ 303 => 'See Other',
21
+ 304 => 'Not Modified',
22
+ 305 => 'Use Proxy',
23
+ 306 => 'Switch Proxy',
24
+ 307 => 'Temporary Redirect',
25
+ 308 => 'Resume Incomplete',
26
+ 400 => 'Bad Request',
27
+ 401 => 'Unauthorized',
28
+ 402 => 'Payment Required',
29
+ 403 => 'Forbidden',
30
+ 404 => 'Not Found',
31
+ 405 => 'Method Not Allowed',
32
+ 406 => 'Not Acceptable',
33
+ 407 => 'Proxy Authentication Required',
34
+ 408 => 'Request Timeout',
35
+ 409 => 'Conflict',
36
+ 410 => 'Gone',
37
+ 411 => 'Length Required',
38
+ 412 => 'Precondition Failed',
39
+ 413 => 'Request Entity Too Large',
40
+ 414 => 'Request-URI Too Long',
41
+ 415 => 'Unsupported Media Type',
42
+ 416 => 'Requested Range Not Satisfiable',
43
+ 417 => 'Expectation Failed',
44
+ 418 => 'I\'m a teapot (RFC 2324)',
45
+ 420 => 'Enhance Your Calm',
46
+ 422 => 'Unprocessable Entity (WebDAV) (RFC 4918)',
47
+ 423 => 'Locked (WebDAV) (RFC 4918)',
48
+ 424 => 'Failed Dependency (WebDAV) (RFC 4918)',
49
+ 426 => 'Upgrade Required (RFC 2817)',
50
+ 428 => 'Precondition Required',
51
+ 429 => 'Too Many Requests',
52
+ 431 => 'Request Header Fields Too Large',
53
+ 444 => 'No Response',
54
+ 449 => 'Retry With',
55
+ 450 => 'Blocked by Windows Parental Controls',
56
+ 451 => 'Wrong Exchange server',
57
+ 499 => 'Client Closed Request',
58
+ 500 => 'Internal Server Error',
59
+ 501 => 'Not Implemented',
60
+ 502 => 'Bad Gateway',
61
+ 503 => 'Service Unavailable',
62
+ 504 => 'Gateway Timeout',
63
+ 505 => 'HTTP Version Not Supported',
64
+ 506 => 'Variant Also Negotiates (RFC 2295)',
65
+ 507 => 'Insufficient Storage (WebDAV) (RFC 4918)',
66
+ 508 => 'Loop Detected (WebDAV) (RFC 5842)',
67
+ 509 => 'Bandwidth Limit Exceeded (Apache bw\/limited extension)',
68
+ 510 => 'Not Extended (RFC 2774)',
69
+ 511 => 'Network Authentication Required',
70
+ 598 => 'Network read timeout error',
71
+ 599 => 'Network connect timeout error'
72
+ }
73
+
74
+ class TestController
75
+ include Lotus::Controller
76
+
77
+ action 'Index' do
78
+ expose :xyz
79
+
80
+ def call(params)
81
+ @xyz = params[:name]
82
+ end
83
+ end
84
+ end
85
+
86
+ class CallAction
87
+ include Lotus::Action
88
+
89
+ def call(params)
90
+ self.status = 201
91
+ self.body = 'Hi from TestAction!'
92
+ self.headers.merge!({ 'X-Custom' => 'OK' })
93
+ end
94
+ end
95
+
96
+ class ErrorCallAction
97
+ include Lotus::Action
98
+
99
+ def call(params)
100
+ raise
101
+ end
102
+ end
103
+
104
+ class ExposeAction
105
+ include Lotus::Action
106
+
107
+ expose :film, :time
108
+
109
+ def call(params)
110
+ @film = '400 ASA'
111
+ end
112
+ end
113
+
114
+ class BeforeMethodAction
115
+ include Lotus::Action
116
+
117
+ expose :article
118
+ before :set_article, :reverse_article
119
+
120
+ def call(params)
121
+ end
122
+
123
+ private
124
+ def set_article
125
+ @article = 'Bonjour!'
126
+ end
127
+
128
+ def reverse_article
129
+ @article.reverse!
130
+ end
131
+ end
132
+
133
+ class SubclassBeforeMethodAction < BeforeMethodAction
134
+ before :upcase_article
135
+
136
+ private
137
+ def upcase_article
138
+ @article.upcase!
139
+ end
140
+ end
141
+
142
+ class ParamsBeforeMethodAction < BeforeMethodAction
143
+ expose :exposed_params
144
+
145
+ private
146
+ def set_article(params)
147
+ @exposed_params = params
148
+ @article = super() + params[:bang]
149
+ end
150
+ end
151
+
152
+ class ErrorBeforeMethodAction < BeforeMethodAction
153
+ private
154
+ def set_article
155
+ raise
156
+ end
157
+ end
158
+
159
+ class BeforeBlockAction
160
+ include Lotus::Action
161
+
162
+ expose :article
163
+ before { @article = 'Good morning!' }
164
+ before { @article.reverse! }
165
+
166
+ def call(params)
167
+ end
168
+ end
169
+
170
+ class YieldBeforeBlockAction < BeforeBlockAction
171
+ expose :yielded_params
172
+ before {|params| @yielded_params = params }
173
+ end
174
+
175
+ class AfterMethodAction
176
+ include Lotus::Action
177
+
178
+ expose :egg
179
+ after :set_egg, :scramble_egg
180
+
181
+ def call(params)
182
+ end
183
+
184
+ private
185
+ def set_egg
186
+ @egg = 'Egg!'
187
+ end
188
+
189
+ def scramble_egg
190
+ @egg = 'gE!g'
191
+ end
192
+ end
193
+
194
+ class SubclassAfterMethodAction < AfterMethodAction
195
+ after :upcase_egg
196
+
197
+ private
198
+ def upcase_egg
199
+ @egg.upcase!
200
+ end
201
+ end
202
+
203
+ class ParamsAfterMethodAction < AfterMethodAction
204
+ private
205
+ def scramble_egg(params)
206
+ @egg = super() + params[:question]
207
+ end
208
+ end
209
+
210
+ class ErrorAfterMethodAction < AfterMethodAction
211
+ private
212
+ def set_egg
213
+ raise
214
+ end
215
+ end
216
+
217
+ class AfterBlockAction
218
+ include Lotus::Action
219
+
220
+ expose :egg
221
+ after { @egg = 'Coque' }
222
+ after { @egg.reverse! }
223
+
224
+ def call(params)
225
+ end
226
+ end
227
+
228
+ class YieldAfterBlockAction < AfterBlockAction
229
+ expose :meaning_of_life_params
230
+ before {|params| @meaning_of_life_params = params }
231
+ end
232
+
233
+ class SessionAction
234
+ include Lotus::Action
235
+ include Lotus::Action::Session
236
+
237
+ def call(params)
238
+ end
239
+ end
240
+
241
+ class RedirectAction
242
+ include Lotus::Action
243
+
244
+ def call(params)
245
+ redirect_to '/destination'
246
+ end
247
+ end
248
+
249
+ class StatusRedirectAction
250
+ include Lotus::Action
251
+
252
+ def call(params)
253
+ redirect_to '/destination', status: 301
254
+ end
255
+ end
256
+
257
+ class GetCookiesAction
258
+ include Lotus::Action
259
+ include Lotus::Action::Cookies
260
+
261
+ def call(params)
262
+ end
263
+ end
264
+
265
+ class SetCookiesAction
266
+ include Lotus::Action
267
+ include Lotus::Action::Cookies
268
+
269
+ def call(params)
270
+ self.body = 'yo'
271
+ cookies[:foo] = 'yum!'
272
+ end
273
+ end
274
+
275
+ class SetCookiesWithOptionsAction
276
+ include Lotus::Action
277
+ include Lotus::Action::Cookies
278
+
279
+ def call(params)
280
+ cookies[:kukki] = { value: 'yum!', domain: 'lotusrb.org', path: '/controller', expires: params[:expires], secure: true, httponly: true }
281
+ end
282
+ end
283
+
284
+ class RemoveCookiesAction
285
+ include Lotus::Action
286
+ include Lotus::Action::Cookies
287
+
288
+ def call(params)
289
+ cookies[:rm] = nil
290
+ end
291
+ end
292
+
293
+ class ThrowCodeAction
294
+ include Lotus::Action
295
+
296
+ def call(params)
297
+ throw params[:status]
298
+ end
299
+ end
300
+
301
+ class ThrowBeforeMethodAction
302
+ include Lotus::Action
303
+
304
+ before :authorize!
305
+ before :set_body
306
+
307
+ def call(params)
308
+ self.body = 'Hello!'
309
+ end
310
+
311
+ private
312
+ def authorize!
313
+ throw 401
314
+ end
315
+
316
+ def set_body
317
+ self.body = 'Hi!'
318
+ end
319
+ end
320
+
321
+ class ThrowBeforeBlockAction
322
+ include Lotus::Action
323
+
324
+ before { throw 401 }
325
+ before { self.body = 'Hi!' }
326
+
327
+ def call(params)
328
+ self.body = 'Hello!'
329
+ end
330
+ end
331
+
332
+ class ThrowAfterMethodAction
333
+ include Lotus::Action
334
+
335
+ after :raise_timeout!
336
+ after :set_body
337
+
338
+ def call(params)
339
+ self.body = 'Hello!'
340
+ end
341
+
342
+ private
343
+ def raise_timeout!
344
+ throw 408
345
+ end
346
+
347
+ def set_body
348
+ self.body = 'Later!'
349
+ end
350
+ end
351
+
352
+ class ThrowAfterBlockAction
353
+ include Lotus::Action
354
+
355
+ after { throw 408 }
356
+ after { self.body = 'Later!' }
357
+
358
+ def call(params)
359
+ self.body = 'Hello!'
360
+ end
361
+ end
362
+
363
+ class RecordNotFound < StandardError
364
+ end
365
+
366
+ class HandledExceptionAction
367
+ include Lotus::Action
368
+ handle_exception RecordNotFound, 404
369
+
370
+ def call(params)
371
+ raise RecordNotFound.new
372
+ end
373
+ end
374
+
375
+ class UnhandledExceptionAction
376
+ include Lotus::Action
377
+
378
+ def call(params)
379
+ raise RecordNotFound.new
380
+ end
381
+ end
382
+
383
+ class ParamsAction
384
+ include Lotus::Action
385
+
386
+ def call(params)
387
+ self.body = params.inspect
388
+ end
389
+ end
390
+
391
+ class Root
392
+ include Lotus::Action
393
+
394
+ def call(params)
395
+ self.body = params.inspect
396
+ headers.merge!({'X-Test' => 'test'})
397
+ end
398
+ end
399
+
400
+ class AboutController
401
+ include Lotus::Controller
402
+
403
+ class Team < Root
404
+ end
405
+
406
+ action 'Contacts' do
407
+ def call(params)
408
+ self.body = params.inspect
409
+ end
410
+ end
411
+ end
412
+
413
+ class IdentityController
414
+ include Lotus::Controller
415
+
416
+ class Action
417
+ include Lotus::Action
418
+
419
+ def call(params)
420
+ self.body = params.inspect
421
+ end
422
+ end
423
+
424
+ Show = Class.new(Action)
425
+ New = Class.new(Action)
426
+ Create = Class.new(Action)
427
+ Edit = Class.new(Action)
428
+ Update = Class.new(Action)
429
+ Destroy = Class.new(Action)
430
+ end
431
+
432
+ class FlowersController
433
+ include Lotus::Controller
434
+
435
+ class Action
436
+ include Lotus::Action
437
+
438
+ def call(params)
439
+ self.body = params.inspect
440
+ end
441
+ end
442
+
443
+ Index = Class.new(Action)
444
+ Show = Class.new(Action)
445
+ New = Class.new(Action)
446
+ Create = Class.new(Action)
447
+ Edit = Class.new(Action)
448
+ Update = Class.new(Action)
449
+ Destroy = Class.new(Action)
450
+ end
451
+
452
+ class DashboardController
453
+ include Lotus::Controller
454
+
455
+ action 'Index' do
456
+ include Lotus::Action::Session
457
+ before :authenticate!
458
+
459
+ def call(params)
460
+ end
461
+
462
+ private
463
+ def authenticate!
464
+ throw 401 unless loggedin?
465
+ end
466
+
467
+ def loggedin?
468
+ session.has_key?(:user_id)
469
+ end
470
+ end
471
+ end
472
+
473
+ class SessionsController
474
+ include Lotus::Controller
475
+
476
+ action 'Create' do
477
+ include Lotus::Action::Session
478
+
479
+ def call(params)
480
+ session[:user_id] = 23
481
+ redirect_to '/'
482
+ end
483
+ end
484
+
485
+ action 'Destroy' do
486
+ include Lotus::Action::Session
487
+
488
+ def call(params)
489
+ session[:user_id] = nil
490
+ end
491
+ end
492
+ end
493
+
494
+ class StandaloneSession
495
+ include Lotus::Action
496
+ include Lotus::Action::Session
497
+
498
+ def call(params)
499
+ session[:age] = Time.now.year - 1982
500
+ end
501
+ end