angelo 0.4.1 → 0.5.0

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.
@@ -1,118 +1,33 @@
1
- require 'erb'
2
- require 'tilt'
1
+ require 'tilt/erb'
3
2
 
4
3
  module Angelo
5
4
  module Tilt
6
5
  module ERB
7
6
 
8
- DEFAULT_LAYOUT = 'layout.%s.erb'
7
+ DEFAULT_LAYOUT = 'layout.%s'
9
8
  DEFAULT_TYPE = :html
10
9
  LAYOUTS_DIR = 'layouts'
11
10
  ACCEPT_ALL = '*/*'
12
11
 
13
- # hrm, sneaky
14
- #
15
- def self.included base
16
- base.extend ClassMethods
17
- end
18
-
19
- module ClassMethods
20
-
21
- @reload_templates = false
22
-
23
- def view_glob *glob
24
- File.join views_dir, *glob
25
- end
26
-
27
- def templatify *glob
28
- Dir[view_glob *glob].reduce({}) do |h,v|
29
- sym = v.gsub views_dir + File::SEPARATOR, ''
30
- return h if (block_given? && yield(v))
31
- sym.gsub! File::SEPARATOR, UNDERSCORE
32
- sym.gsub! /\.\w+?\.erb$/, EMPTY_STRING
33
- sym.gsub! /^#{LAYOUTS_DIR}#{UNDERSCORE}/, EMPTY_STRING
34
- h[sym.to_sym] = ::Tilt::ERBTemplate.new v
35
- h
36
- end
37
- end
38
-
39
- def templates type = DEFAULT_TYPE
40
- @templates ||= {}
41
- if reload_templates?
42
- @templates[type] = load_templates(type)
43
- else
44
- @templates[type] ||= load_templates(type)
45
- end
46
- end
47
-
48
- def load_templates type = DEFAULT_TYPE
49
- templatify('**', "*.#{type}.erb") do |v|
50
- v =~ /^#{LAYOUTS_DIR}#{File::SEPARATOR}/
51
- end
52
- end
53
-
54
- def layout_templates type = DEFAULT_TYPE
55
- if reload_templates?
56
- @layout_templates = load_layout_templates(type)
57
- else
58
- @layout_templates ||= load_layout_templates(type)
59
- end
60
- end
12
+ def erb view, opts = {}
13
+ type = opts.delete(:type) || template_type
14
+ content_type type
61
15
 
62
- def load_layout_templates type = DEFAULT_TYPE
63
- templatify LAYOUTS_DIR, "*.#{type}.erb"
16
+ if view.is_a? Symbol
17
+ view = :"#{view}.#{type}"
64
18
  end
65
19
 
66
- def default_layout type = DEFAULT_TYPE
67
- @default_layout ||= {}
68
- if reload_templates?
69
- @default_layout[type] = load_default_layout(type)
20
+ layout =
21
+ case opts[:layout]
22
+ when false
23
+ false
24
+ when Symbol
25
+ :"#{LAYOUTS_DIR}/#{layout}.#{type}"
70
26
  else
71
- @default_layout[type] ||= load_default_layout(type)
27
+ :"#{DEFAULT_LAYOUT % type}"
72
28
  end
73
- end
74
-
75
- def load_default_layout type = DEFAULT_TYPE
76
- l = view_glob(DEFAULT_LAYOUT % type)
77
- ::Tilt::ERBTemplate.new l if File.exist? l
78
- end
79
-
80
- def reload_templates! on = true
81
- @reload_templates = on
82
- end
83
-
84
- def reload_templates?
85
- @reload_templates
86
- end
87
29
 
88
- end
89
-
90
- def erb view, opts = {locals: {}}
91
- type = opts[:type] || template_type
92
- content_type type
93
- locals = Hash === opts[:locals] ? opts[:locals] : {}
94
- render = case view
95
- when String
96
- ->{ view }
97
- when Symbol
98
- ->{self.class.templates(type)[view].render self, locals}
99
- end
100
- case opts[:layout]
101
- when false
102
- render[]
103
- when Symbol
104
- if lt = self.class.layout_templates(type)[opts[:layout]]
105
- lt.render self, locals, &render
106
- else
107
- raise ArgumentError.new "unknown layout - :#{opts[:layout]}"
108
- end
109
- else
110
- if self.class.default_layout(type)
111
- self.class.default_layout(type).render self, locals, &render
112
- else
113
- render[]
114
- end
115
- end
30
+ _erb view, layout: layout, locals: opts[:locals]
116
31
  end
117
32
 
118
33
  def template_type
@@ -1,3 +1,3 @@
1
1
  module Angelo
2
- VERSION = '0.4.1'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -1,3 +1,9 @@
1
+ # Having to require this sucks because tilt is an implementation
2
+ # detail we shouldn't have to know about. But this avoids a "tilt
3
+ # autoloading 'tilt/erb' in a non thread-safe way" wanring.
4
+ #
5
+ require 'tilt/erb'
6
+
1
7
  require_relative '../spec_helper'
2
8
 
3
9
  describe Angelo::Base do
@@ -90,6 +90,10 @@ describe Angelo::Base do
90
90
  @bat = params[:bat] if @foo
91
91
  end
92
92
 
93
+ before %r{/before/(\d+)} do
94
+ @id = params[:id].to_i
95
+ end
96
+
93
97
  [:get, :post, :put].each do |m|
94
98
 
95
99
  __send__ m, '/before' do
@@ -106,6 +110,11 @@ describe Angelo::Base do
106
110
  content_type :json
107
111
  { foo: @foo, bar: @bar, bat: @bat }.select! {|k,v| !v.nil?}
108
112
  end
113
+
114
+ __send__ m, '/before/:id' do
115
+ content_type :json
116
+ { id: @id }
117
+ end
109
118
  end
110
119
 
111
120
  end
@@ -142,6 +151,14 @@ describe Angelo::Base do
142
151
 
143
152
  end
144
153
 
154
+ it 'matches regexes' do
155
+ [:get, :post, :put].each do |m|
156
+ id = rand 1000
157
+ __send__ m, "/before/#{id}"
158
+ last_response_must_be_json({'id' => id})
159
+ end
160
+ end
161
+
145
162
  end
146
163
 
147
164
  end
@@ -67,6 +67,27 @@ describe Angelo::ParamsParser do
67
67
  parser.parse_post_body.must_equal({})
68
68
  end
69
69
 
70
+ it "doesn't barf on JSON array POST bodies" do
71
+ parser.form_encoded = false
72
+ parser.json = true
73
+ parser.body = [123,234].to_json
74
+ ->{ parser.parse_post_body }.must_be_silent
75
+ end
76
+
77
+ it 'raises properly on malformed JSON' do
78
+ parser.form_encoded = false
79
+ parser.json = true
80
+ parser.body = "{F(34nfnlv,-935;:fho2fhlj}}}function(){console.log('hi');}"
81
+ ->{ parser.parse_post_body }.must_raise JSON::ParserError
82
+ end
83
+
84
+ it 'parses JSON array POST bodies' do
85
+ parser.form_encoded = false
86
+ parser.json = true
87
+ parser.body = [123,234].to_json
88
+ parser.parse_post_body.must_equal [123,234]
89
+ end
90
+
70
91
  it 'recursively symhashes JSON POST bodies params' do
71
92
  nested = {
72
93
  foo: {
@@ -25,23 +25,53 @@ describe Angelo::Stash do
25
25
  stashes[@context] << s
26
26
  end
27
27
 
28
+ def self.clear
29
+ stashes.values.each &:clear
30
+ end
31
+
28
32
  end
29
33
 
30
- it 'does not skip live sockets when removing dead sockets' do
31
- err_sock = ErrorSocket.new
34
+ after do
35
+ TestStash.clear
36
+ end
32
37
 
38
+ def mock_good_sock
33
39
  good_sock = Minitest::Mock.new
34
40
  good_sock.expect :read, "hi"
35
41
  good_sock.expect :hash, 123
36
- def good_sock.== o; o == self; end
42
+ def good_sock.== o
43
+ o == self
44
+ end
45
+ def good_sock.eql? o
46
+ self.object_id == o.object_id
47
+ end
48
+ good_sock
49
+ end
37
50
 
51
+ it 'does not skip live sockets when removing dead sockets' do
38
52
  stash = TestStash.new nil
53
+ good_sock = mock_good_sock
54
+ err_sock = ErrorSocket.new
39
55
 
40
56
  stash << err_sock
41
57
  stash << good_sock
42
58
 
43
59
  stash.each {|s| s.read}
44
60
  good_sock.verify
61
+ stash.each {|s| err_sock.wont_equal s}
62
+ end
63
+
64
+ it 'removes sockets from contexts during all_each' do
65
+ stash = TestStash.new nil
66
+ good_sock = mock_good_sock
67
+ err_sock = ErrorSocket.new
68
+
69
+ stash[:foo] << err_sock
70
+ stash[:bar] << good_sock
71
+
72
+ stash.all_each {|s| s.read}
73
+ good_sock.verify
74
+ stash.all_each {|s| err_sock.wont_equal s}
45
75
  end
46
76
 
47
77
  end
@@ -14,10 +14,10 @@ describe Angelo::Responder::Websocket do
14
14
  end
15
15
  action = (key.to_s + '_go').to_sym
16
16
  Reactor.define_action action do |n|
17
- every(0.01){ terminate if Reactor.stop? }
18
17
  Reactor.testers[key][n].go
19
18
  end
20
19
  Reactor.unstop!
20
+ $reactor.async.wait_for_stop
21
21
  CONCURRENCY.times {|n| $reactor.async.__send__(action, n)}
22
22
 
23
23
  sleep 0.01 * CONCURRENCY
@@ -51,10 +51,10 @@ describe Angelo::Responder::Websocket do
51
51
  wsh.init
52
52
  Reactor.testers[:tester] = wsh
53
53
  Reactor.define_action :go do
54
- every(0.01){ terminate if Reactor.stop? }
55
54
  Reactor.testers[:tester].go
56
55
  end
57
56
  Reactor.unstop!
57
+ $reactor.async.wait_for_stop
58
58
  $reactor.async.go
59
59
 
60
60
  500.times {|n| wsh.text "hi there #{n}"}
@@ -69,7 +69,7 @@ describe Angelo::Responder::Websocket do
69
69
  it 'responds on multiple websockets properly' do
70
70
  latch = CountDownLatch.new CONCURRENCY * 500
71
71
 
72
- Reactor.testers[:wshs] = Array.new(CONCURRENCY).map do
72
+ Reactor.testers[:wshs] = Array.new CONCURRENCY do
73
73
  wsh = websocket_helper '/'
74
74
  wsh.on_message = ->(e) {
75
75
  assert_match /hi there \d/, e.data
@@ -80,18 +80,18 @@ describe Angelo::Responder::Websocket do
80
80
  end
81
81
 
82
82
  Reactor.define_action :go do |n|
83
- every(0.01){ terminate if Reactor.stop? }
84
83
  Reactor.testers[:wshs][n].go
85
84
  end
86
85
  Reactor.unstop!
86
+ $reactor.async.wait_for_stop
87
87
  CONCURRENCY.times {|n| $reactor.async.go n}
88
88
 
89
89
  sleep 0.01 * CONCURRENCY
90
90
 
91
- ActorPool.define_action :go do |n|
91
+ Actor.define_action :go do |n|
92
92
  500.times {|x| Reactor.testers[:wshs][n].text "hi there #{x}"}
93
93
  end
94
- CONCURRENCY.times {|n| $pool.async.go n}
94
+ CONCURRENCY.times {|n| $pool[n].async.go n}
95
95
  latch.wait
96
96
 
97
97
  Reactor.testers[:wshs].map &:close
@@ -99,7 +99,7 @@ describe Angelo::Responder::Websocket do
99
99
  Reactor.testers.delete :wshs
100
100
  Reactor.remove_action :go
101
101
 
102
- ActorPool.remove_action :go
102
+ Actor.remove_action :go
103
103
  end
104
104
 
105
105
  end
@@ -127,10 +127,10 @@ describe Angelo::Responder::Websocket do
127
127
  wsh.init
128
128
  Reactor.testers[:tester] = wsh
129
129
  Reactor.define_action :go do
130
- every(0.01){ terminate if Reactor.stop? }
131
130
  Reactor.testers[:tester].go
132
131
  end
133
132
  Reactor.unstop!
133
+ $reactor.async.wait_for_stop
134
134
  $reactor.async.go
135
135
 
136
136
  latch.wait
@@ -168,10 +168,10 @@ describe Angelo::Responder::Websocket do
168
168
  wsh.init
169
169
  Reactor.testers[:tester] = wsh
170
170
  Reactor.define_action :go do
171
- every(0.01){ terminate if Reactor.stop? }
172
171
  Reactor.testers[:tester].go
173
172
  end
174
173
  Reactor.unstop!
174
+ $reactor.async.wait_for_stop
175
175
  $reactor.async.go
176
176
 
177
177
  latch.wait
@@ -286,7 +286,7 @@ describe Angelo::Responder::Websocket do
286
286
 
287
287
  latch = CountDownLatch.new CONCURRENCY
288
288
 
289
- Reactor.testers[:hmc] = Array.new(CONCURRENCY).map do
289
+ Reactor.testers[:hmc] = Array.new CONCURRENCY do
290
290
  wsh = websocket_helper '/'
291
291
  wsh.on_message = ->(e) {
292
292
  wait_for_block[e]
@@ -298,7 +298,7 @@ describe Angelo::Responder::Websocket do
298
298
 
299
299
  one_latch = CountDownLatch.new CONCURRENCY
300
300
 
301
- Reactor.testers[:hmc_one] = Array.new(CONCURRENCY).map do
301
+ Reactor.testers[:hmc_one] = Array.new CONCURRENCY do
302
302
  wsh = websocket_helper '/one'
303
303
  wsh.on_message = ->(e) {
304
304
  wait_for_block[e]
@@ -310,7 +310,7 @@ describe Angelo::Responder::Websocket do
310
310
 
311
311
  other_latch = CountDownLatch.new CONCURRENCY
312
312
 
313
- Reactor.testers[:hmc_other] = Array.new(CONCURRENCY).map do
313
+ Reactor.testers[:hmc_other] = Array.new CONCURRENCY do
314
314
  wsh = websocket_helper '/other'
315
315
  wsh.on_message = ->(e) {
316
316
  wait_for_block[e]
data/test/angelo_spec.rb CHANGED
@@ -23,6 +23,10 @@ describe Angelo::Base do
23
23
  redirect '/'
24
24
  end
25
25
 
26
+ get '/redirect_forever' do
27
+ redirect! '/'
28
+ end
29
+
26
30
  get '/wait' do
27
31
  sleep 3
28
32
  nil
@@ -49,6 +53,12 @@ describe Angelo::Base do
49
53
 
50
54
  it 'redirects' do
51
55
  get '/redirect'
56
+ last_response.status.must_equal 302
57
+ last_response.headers['Location'].must_equal '/'
58
+ end
59
+
60
+ it 'redirects permanently' do
61
+ get '/redirect_forever'
52
62
  last_response.status.must_equal 301
53
63
  last_response.headers['Location'].must_equal '/'
54
64
  end
@@ -58,29 +68,29 @@ describe Angelo::Base do
58
68
  get_end = nil
59
69
  latch = CountDownLatch.new 2
60
70
 
61
- ActorPool.define_action :do_wait do
71
+ Actor.define_action :do_wait do
62
72
  get '/wait'
63
73
  wait_end = Time.now
64
74
  latch.count_down
65
75
  end
66
76
 
67
- ActorPool.define_action :do_get do
77
+ Actor.define_action :do_get do
68
78
  sleep 1
69
79
  get '/'
70
80
  get_end = Time.now
71
81
  latch.count_down
72
82
  end
73
83
 
74
- ActorPool.unstop!
75
- $pool.async :do_wait
76
- $pool.async :do_get
84
+ Actor.unstop!
85
+ $pool[0].async :do_wait
86
+ $pool[1].async :do_get
77
87
 
78
88
  latch.wait
79
89
  get_end.must_be :<, wait_end
80
90
 
81
- ActorPool.stop!
82
- ActorPool.remove_action :do_wait
83
- ActorPool.remove_action :do_get
91
+ Actor.stop!
92
+ Actor.remove_action :do_wait
93
+ Actor.remove_action :do_get
84
94
  end
85
95
 
86
96
  it 'does not crash when receiving unknown http request type' do
@@ -318,6 +328,11 @@ describe Angelo::Base do
318
328
  end
319
329
  end
320
330
 
331
+ post '/json_array' do
332
+ content_type :json
333
+ {params: params, body: request_body}
334
+ end
335
+
321
336
  end
322
337
 
323
338
  it 'parses formencoded body when content-type is formencoded' do
@@ -349,6 +364,19 @@ describe Angelo::Base do
349
364
  end
350
365
  end
351
366
 
367
+ it 'parses JSON array bodies but does not merge into params' do
368
+ post '/json_array?foo=bar', [123,234].to_json, {'Content-Type' => Angelo::JSON_TYPE}
369
+ last_response_must_be_json({
370
+ "params" => {"foo" => "bar"},
371
+ "body" => [123,234]
372
+ })
373
+ end
374
+
375
+ it 'does not die on malformed JSON' do
376
+ post '/json_array?foo=bar', '{]sdfj2if08yth4j]j,:jsd;f; function()', {'Content-Type' => Angelo::JSON_TYPE}
377
+ last_response.status.must_equal 400
378
+ end
379
+
352
380
  end
353
381
 
354
382
  describe 'request_headers helper' do
@@ -555,6 +583,20 @@ describe Angelo::Base do
555
583
 
556
584
  end
557
585
 
586
+ describe 'default_headers' do
587
+
588
+ define_app do
589
+ default_headers "Access-Control-Allow-Origin" => "*"
590
+ get('/'){ 'hi' }
591
+ end
592
+
593
+ it 'adds a default headers' do
594
+ get '/'
595
+ last_response.headers['Access-Control-Allow-Origin'].must_equal "*"
596
+ end
597
+
598
+ end
599
+
558
600
  end
559
601
 
560
602
  end