commute 0.2.0.rc.2 → 0.3.0.pre

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.
Files changed (66) hide show
  1. data/.todo +28 -12
  2. data/README.md +0 -1
  3. data/commute.gemspec +4 -5
  4. data/lib/commute/common/basic_auth.rb +10 -9
  5. data/lib/commute/common/caching.rb +208 -0
  6. data/lib/commute/common/chemicals.rb +47 -24
  7. data/lib/commute/common/eventmachine.rb +68 -0
  8. data/lib/commute/common/synchrony.rb +42 -0
  9. data/lib/commute/common/typhoeus.rb +64 -0
  10. data/lib/commute/core/api.rb +42 -29
  11. data/lib/commute/core/builder.rb +4 -15
  12. data/lib/commute/core/context.rb +156 -15
  13. data/lib/commute/core/http.rb +124 -0
  14. data/lib/commute/core/layer.rb +187 -0
  15. data/lib/commute/core/sequence.rb +83 -132
  16. data/lib/commute/core/stack.rb +63 -72
  17. data/lib/commute/core/status.rb +45 -0
  18. data/lib/commute/core/util/event_emitter.rb +58 -0
  19. data/lib/commute/core/util/path.rb +37 -0
  20. data/lib/commute/core/util/stream.rb +141 -0
  21. data/lib/commute/extensions/crud.rb +88 -0
  22. data/lib/commute/extensions/param.rb +20 -0
  23. data/lib/commute/extensions/url.rb +53 -0
  24. data/lib/commute/version.rb +1 -1
  25. data/spec/commute/common/caching_spec.rb +158 -0
  26. data/spec/commute/common/eventmachine_spec.rb +74 -0
  27. data/spec/commute/common/typhoeus_spec.rb +67 -0
  28. data/spec/commute/core/api_spec.rb +3 -1
  29. data/spec/commute/core/builder_spec.rb +8 -8
  30. data/spec/commute/core/http_spec.rb +39 -0
  31. data/spec/commute/core/layer_spec.rb +81 -0
  32. data/spec/commute/core/sequence_spec.rb +36 -150
  33. data/spec/commute/core/stack_spec.rb +33 -83
  34. data/spec/commute/core/util/event_emitter_spec.rb +35 -0
  35. data/spec/commute/core/util/path_spec.rb +29 -0
  36. data/spec/commute/core/util/stream_spec.rb +90 -0
  37. data/spec/commute/extensions/url_spec.rb +76 -0
  38. data/spec/spec_helper.rb +3 -1
  39. metadata +61 -48
  40. data/examples/gist_api.rb +0 -71
  41. data/examples/highrise_task_api.rb +0 -59
  42. data/examples/pastie_api.rb +0 -18
  43. data/lib/commute/aspects/caching.rb +0 -37
  44. data/lib/commute/aspects/crud.rb +0 -41
  45. data/lib/commute/aspects/pagination.rb +0 -16
  46. data/lib/commute/aspects/url.rb +0 -57
  47. data/lib/commute/common/cache.rb +0 -43
  48. data/lib/commute/common/conditional.rb +0 -27
  49. data/lib/commute/common/em-synchrony_adapter.rb +0 -29
  50. data/lib/commute/common/em_http_request_adapter.rb +0 -57
  51. data/lib/commute/common/typhoeus_adapter.rb +0 -40
  52. data/lib/commute/common/xml.rb +0 -7
  53. data/lib/commute/core/commuter.rb +0 -116
  54. data/lib/commute/core/processors/code_status_processor.rb +0 -40
  55. data/lib/commute/core/processors/hook.rb +0 -14
  56. data/lib/commute/core/processors/request_builder.rb +0 -26
  57. data/lib/commute/core/processors/sequencer.rb +0 -46
  58. data/lib/commute/core/request.rb +0 -58
  59. data/lib/commute/core/response.rb +0 -18
  60. data/spec/commute/aspects/caching_spec.rb +0 -12
  61. data/spec/commute/aspects/url_spec.rb +0 -61
  62. data/spec/commute/core/commuter_spec.rb +0 -64
  63. data/spec/commute/core/processors/code_status_processor_spec.rb +0 -5
  64. data/spec/commute/core/processors/hook_spec.rb +0 -25
  65. data/spec/commute/core/processors/request_builder_spec.rb +0 -25
  66. data/spec/commute/core/processors/sequencer_spec.rb +0 -33
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ require 'commute/core/http'
4
+
5
+ describe Commute::Http::Status do
6
+
7
+ Status = Commute::Http::Status
8
+
9
+ it 'should be a failure for 4xx and 5xx status codes' do
10
+ Status.new(420).fail?.must_equal true
11
+ Status.new(501).fail?.must_equal true
12
+ Status.new(501).success?.must_equal false
13
+ end
14
+
15
+ it 'should be a success otherwise' do
16
+ Status.new(200).success?.must_equal true
17
+ Status.new(200).fail?.must_equal false
18
+ Status.new(301).success?.must_equal true
19
+ end
20
+ end
21
+
22
+ describe Commute::Http::Request do
23
+
24
+ it 'should have some default values' do
25
+ request = Commute::Http::Request.new nil
26
+ request.uri.path.must_equal '/'
27
+ request.headers.must_be_empty
28
+ request.query.must_be_empty
29
+ request.method.must_equal :get
30
+ end
31
+
32
+ it 'should keep uri#query consistent' do
33
+ request = Commute::Http::Request.new nil
34
+ request.query[:limit] = 10
35
+ request.uri.query.must_equal 'limit%3D10'
36
+ request.query[:index] = 20
37
+ request.uri.query.must_equal 'limit%3D10%26index%3D20'
38
+ end
39
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ require 'commute/core/builder'
4
+ require 'commute/core/http'
5
+ require 'commute/core/util/path'
6
+ require 'commute/core/layer'
7
+
8
+ describe Commute::Layer do
9
+
10
+ it 'should have a name' do
11
+ layer = Commute::Layer.new proc {}, :test
12
+ layer.name.must_equal :test
13
+ end
14
+
15
+ it 'should figure out the name is none was given' do
16
+ # With an instance var.
17
+ klass = Class.new do
18
+ @name = :test
19
+ end
20
+ layer = Commute::Layer.new klass.new
21
+ layer.name.must_equal :test
22
+ # With a method.
23
+ klass = Class.new do
24
+ def name; :test; end
25
+ end
26
+ layer = Commute::Layer.new klass.new
27
+ layer.name.must_equal :test
28
+ end
29
+
30
+ describe '#call' do
31
+
32
+ it 'should auto-add context options to the options hash' do
33
+ context = Commute::Context.new(nil).with auth: { key: 'secret' }
34
+ request = Commute::Layer::Request.new Commute::Http::Request.new(context)
35
+ callable = mock
36
+ router = mock
37
+ callable.expects(:call).with(router, request, key: 'secret')
38
+ Commute::Layer.new(callable, :auth).call router, request
39
+ end
40
+ end
41
+
42
+ describe '#callable' do
43
+ let(:context) { Commute::Context.new(nil) }
44
+
45
+ it 'should return false if the layer is disabled' do
46
+ Commute::Layer.new(proc {}, :test).callable?(context.disable(:test).context).must_equal false
47
+ end
48
+
49
+ it 'should return false if the layer has no callable' do
50
+ Commute::Layer.new(nil, :test).callable?(context).must_equal false
51
+ end
52
+
53
+ it 'should return true otherwise' do
54
+ Commute::Layer.new(proc {}, :test).callable?(context).must_equal true
55
+ end
56
+ end
57
+ end
58
+
59
+ describe Commute::Layer::Communication do
60
+
61
+ it 'should hold an underlaying http request/response' do
62
+ http = Commute::Http::Request.new(nil)
63
+ Commute::Layer::Request.new(http).http.must_equal http
64
+ end
65
+ end
66
+
67
+ describe Commute::Layer::Request do
68
+
69
+ it 'should be respondable' do
70
+ receiver = proc {}
71
+ http_request = Commute::Http::Request.new(nil)
72
+ request = Commute::Layer::Request.new(http_request)
73
+ http_response = Commute::Http::Response.new(request)
74
+ status = Commute::Status.ok
75
+ request.on(:response, &receiver)
76
+ receiver.expects(:call).with do |response, _status|
77
+ response.http == http_response && _status == status
78
+ end
79
+ request.respond http_response, status
80
+ end
81
+ end
@@ -3,188 +3,74 @@ require 'commute/core/sequence'
3
3
 
4
4
  describe Commute::Sequence do
5
5
 
6
- let(:sequence) { Commute::Sequence.new }
6
+ let(:sequence) { Commute::Sequence.new(:test) }
7
7
 
8
- let(:processor1) {
9
- lambda { |result, subject, options = {}| result + 1 }
10
- }
11
-
12
- let(:processor2) {
13
- lambda { |result, subject, options = {}| result * 2 }
14
- }
15
-
16
- let(:processor3) {
17
- Class.new {
18
- @id = :test
19
-
20
- def call result, subject, options = {}
21
- result ** options[:power]
22
- end
23
- }.new
24
- }
8
+ it 'should have a name' do
9
+ sequence.name.must_equal :test
10
+ end
25
11
 
26
12
  describe '#new' do
13
+ it 'should be empty' do
14
+ sequence.size.must_equal 0
15
+ end
16
+
27
17
  it 'should alter the sequence when a block is provided' do
28
- sequence = Commute::Sequence.new do |s|
29
- s.append processor1
30
- end
31
- sequence.processors.size.must_equal 1
18
+ Commute::Sequence.new(:test) { |s|
19
+ s.append proc {}
20
+ }.size.must_equal 1
21
+ Commute::Sequence.new(:test) {
22
+ append proc {}
23
+ append proc {}
24
+ }.size.must_equal 2
32
25
  end
33
26
  end
34
27
 
35
28
  describe '#alter' do
36
29
  it 'should alter the sequence in a block when the arity is 1' do
37
- sequence.alter do |s|
38
- s.append processor1
39
- end
40
- sequence.processors.size.must_equal 1
30
+ sequence.alter { |s|
31
+ s.append proc {}
32
+ }.size.must_equal 1
41
33
  end
42
34
 
43
35
  it 'should evaluate the block when the arity is 0' do
44
- sequence.alter do
45
- append Proc.new { |n| n.data += 1 }
46
- end
47
- sequence.processors.size.must_equal 1
48
- end
49
- end
50
-
51
- describe '#processors' do
52
- it 'should return an empty array when there are no processors' do
53
- sequence.processors.must_equal []
54
- end
55
-
56
- it 'should not return a modifiable array' do
57
- sequence.processors << 'test'
58
- sequence.processors.must_be_empty
59
- end
60
- end
61
-
62
- describe '#processors=' do
63
-
64
- it 'should not exist' do
65
- Proc.new { sequence.processors = nil }.must_raise NoMethodError
66
- end
67
- end
68
-
69
- describe '#call' do
70
- let(:processor1) do
71
- Proc.new do |commuter|
72
- commuter.change { |n| n+1 }
73
- end
74
- end
75
-
76
- let(:processor2) do
77
- Proc.new do |commuter, operation|
78
- commuter.change { |n| n.send operation, n }
79
- end
80
- end
81
-
82
- let(:commuter) { Commute::Commuter.new mock, 1 }
83
-
84
- it 'should call all processors' do
85
- commuter.expects(:parameters).with(nil).never
86
- commuter.expects(:parameters).with(:operator).returns(:**)
87
- sequence.append(processor1)
88
- sequence.append(processor2, as: :operator)
89
- sequence.call commuter
90
- commuter.get.must_equal 4
91
- end
92
-
93
- # This is not tested implicitely due to the Proc usage.
94
- it 'should call processors with the correct number of arguments' do
95
- commuter.expects(:parameters).with(:operator).returns(:+)
96
- processor1 = mock
97
- processor1.expects(:call).with(commuter)
98
- processor2 = mock
99
- processor2.expects(:call).with(commuter, :+)
100
- sequence.append processor1
101
- sequence.append processor2, as: :operator
102
- sequence.call commuter
103
- end
104
-
105
- it 'should be a processor itself' do
106
- commuter.expects(:parameters).with(:operator).returns(:**)
107
-
108
- subsequence = Commute::Sequence.new do |s|
109
- s.append processor1
110
- end
111
-
112
- sequence = Commute::Sequence.new do |s|
113
- s.append subsequence
114
- s.append processor2, as: :operator
115
- end
116
-
117
- sequence.call commuter
118
- commuter.get.must_equal 4
119
- end
120
-
121
- it 'should return nothing' do
122
- Commute::Sequence.new { |s|
123
- s.append processor1
124
- }.call(commuter).must_equal nil
36
+ sequence.alter {
37
+ append proc {}
38
+ }.size.must_equal 1
125
39
  end
126
40
  end
127
41
 
128
42
  describe '#append' do
129
43
  it 'should append the processor to the tail' do
130
- sequence.append processor1
131
- sequence.processors.must_equal [processor1]
132
- assert_equal sequence.append(processor2), processor2
133
- sequence.processors.must_equal [processor1, processor2]
134
- end
135
-
136
- it 'should be able to override the default processor id' do
137
- sequence.append processor1, as: :increment
138
- sequence.at(:increment).call(1, nil).must_equal 2
139
-
140
- sequence.append processor3, as: :power
141
- sequence.at(:power).must_equal processor3
44
+ layer1 = sequence.append proc {}
45
+ sequence.must_equal [layer1]
46
+ layer2 = sequence.append proc {}
47
+ sequence.must_equal [layer1, layer2]
142
48
  end
143
49
 
144
50
  it 'should be able to append after a specified processor' do
145
- sequence.append processor1, as: :increment
146
- sequence.append processor2
51
+ layer1 = sequence.append proc {}, as: :action1
52
+ layer3 = sequence.append proc {}
147
53
 
148
- sequence.append processor3, as: :power, after: :increment
149
- sequence.processors.must_equal [processor1, processor3, processor2]
54
+ layer2 = sequence.append proc {}, as: :action2, after: :action1
55
+ sequence.must_equal [layer1, layer2, layer3]
150
56
  end
151
57
  end
152
58
 
153
59
  describe '#remove' do
154
60
  before do
155
- sequence.append processor1
156
- sequence.append processor2, as: :multiplier
157
- end
158
-
159
- it 'should remove the processor if the processor itself is given' do
160
- processor = sequence.remove processor1
161
- (processor == processor1).must_equal true
162
- sequence.processors.must_equal [processor2]
163
- assert_equal sequence.at(:multiplier), processor2
61
+ @layer1 = sequence.append proc {}
62
+ @layer2 = sequence.append proc {}, as: :action
164
63
  end
165
64
 
166
65
  it 'should remove the processor if its id is given' do
167
- processor = sequence.remove :multiplier
168
- (processor == processor2).must_equal true
169
- sequence.processors.must_equal [processor1]
170
- sequence.at(:multiplier).must_be_nil
66
+ sequence.remove(:action).must_equal @layer2
67
+ sequence.must_equal [@layer1]
171
68
  end
172
69
  end
173
70
 
174
- describe '#at' do
175
- it 'should index processors with a default id' do
176
- sequence.append processor3
177
- sequence.at(:test).must_equal processor3
178
- end
179
- end
180
-
181
- describe '#dup' do
182
- it 'should duplicate the sequence' do
183
- sequence.append processor1
184
- cloned = sequence.dup
185
- cloned.append processor2
186
- sequence.processors.size.must_equal 1
187
- cloned.processors.size.must_equal 2
71
+ describe '#call' do
72
+ it 'should lazily push the sequence on the path' do
73
+ skip
188
74
  end
189
75
  end
190
76
  end
@@ -4,93 +4,43 @@ require 'commute/core/stack'
4
4
 
5
5
  describe Commute::Stack do
6
6
 
7
- let(:sequence) do
8
- Commute::Sequence.new do
9
- append Proc.new { |n| n.data += 1 }
10
- end
11
- end
12
-
13
- let(:stack) do
14
- Commute::Stack.new do |stack, main|
15
- end
16
- end
17
-
18
- describe '#initialize' do
19
- describe 'when no sequence is given' do
20
- it 'should yield the main sequence for the stack when the arity is 1' do
21
- stack = Commute::Stack.new do |sequence|
22
- sequence.append Proc.new {}
7
+ let(:stack) { Commute::Stack.new }
8
+
9
+ # A Layer that forwards to the next.
10
+ let(:forward_layer) { proc { |router, request, options = {}|
11
+ request.pipe router.call(request.http) { |response, status|
12
+ response.pipe request.respond(response.http, status)
13
+ }
14
+ }}
15
+
16
+ # A layer that echoes all data.
17
+ let(:echo_layer) { proc { |router, request, options = {}|
18
+ request.pipe request.respond(Commute::Http::Response.new(request), Commute::Status.ok)
19
+ }}
20
+
21
+ describe '#call' do
22
+ describe 'with a forward and echo layer' do
23
+ it 'should echo all data' do
24
+ # Append a forward and echo layer to the main sequence.
25
+ stack.sequence do |s|
26
+ s.append forward_layer
27
+ s.append forward_layer
28
+ s.append stack.sequence(:echo)
23
29
  end
24
- stack.sequence.processors.size.must_equal 1
25
- end
26
-
27
- it 'should yield the stack and the main sequence when the arity is 2' do
28
- stack = Commute::Stack.new do |stack, sequence|
29
- sequence.append Proc.new {}
30
+ stack.sequence(:echo) do |s|
31
+ s.append echo_layer
30
32
  end
31
- stack.sequence.processors.size.must_equal 1
32
- end
33
- end
34
-
35
- describe 'when a sequence is given' do
36
- it 'should use that sequence' do
37
- sequence = Commute::Sequence.new do
38
- append Proc.new {}
39
- end
40
- stack = Commute::Stack.new sequence
41
- stack.sequence.processors.size.must_equal 1
42
- end
43
- end
44
- end
45
-
46
- describe '#add' do
47
- it 'should add a sequence to the stack by name' do
48
- stack.add sequence, :calculation
49
- stack.sequence(:calculation).must_equal sequence
50
- end
51
- end
52
-
53
- describe '#sequence' do
54
- it 'should add the sequence when it was not found' do
55
- stack.sequence(:calculation) do
56
- append Proc.new { |c| c.set(c.get + 1) }
57
- end
58
-
59
- stack.sequence(:calculation).processors.size.must_equal 1
60
- end
61
- end
62
-
63
- describe '#sequencer' do
64
- it 'should allow us to embed a sequence in a sequence' do
65
- stack = Commute::Stack.new do |stack, sequence|
66
- sequence.append Commute::Sequencer.new(:calculation)
67
- sequence.append Proc.new { |c|
68
- c.change { |n| "The result is #{n}" }
69
- }
70
-
71
- stack.sequence(:calculation) do
72
- append Proc.new { |c| c.set(c.get + 1) }
73
- append Proc.new { |c| c.set(c.get + 2) }
33
+ # Call the stack.
34
+ context = Commute::Context.new stack
35
+ request = stack.call Commute::Http::Request.new(context) do |response, status|
36
+ puts 'response!'
37
+ response.on(:data) { |chunk| puts chunk }
38
+ response.on(:end) { puts 'end!'}
74
39
  end
40
+ request.write 1
41
+ request.write 2
42
+ request.end
75
43
  end
76
- commuter = Commute::Commuter.new(Commute::Context.new(stack), 1)
77
- stack.call(commuter)
78
- commuter.get.must_equal 'The result is 4'
79
- end
80
- end
81
-
82
- describe '#dup' do
83
- it 'should duplicate the stack and all its sequences' do
84
- stack.add sequence, :calculation
85
- cloned = stack.dup.alter do
86
- sequence.append Proc.new {}
87
-
88
- sequence(:calculation).append Proc.new {}
89
- end
90
- stack.sequence.processors.size.must_equal 0
91
- stack.sequence(:calculation).processors.size.must_equal 1
92
- cloned.sequence.processors.size.must_equal 1
93
- cloned.sequence(:calculation).processors.size.must_equal 2
94
44
  end
95
45
  end
96
46
  end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ require 'commute/core/util/event_emitter'
4
+
5
+ describe Commute::EventEmitter do
6
+
7
+ let(:emitter) {
8
+ Class.new { include Commute::EventEmitter }.new
9
+ }
10
+
11
+ let(:handler1) { proc {} }
12
+ let(:handler2) { proc {} }
13
+ let(:handler3) { proc {} }
14
+
15
+ it 'should notify event handlers for one event' do
16
+ [handler1, handler2].each do |handler|
17
+ handler.expects(:call).with :test, 1
18
+ end
19
+ emitter.on :data, &handler1
20
+ emitter.on :data, &handler2
21
+ emitter.send :emit, :data, :test, 1
22
+ end
23
+
24
+ it 'should notify event handlers for multiple events' do
25
+ [handler1, handler2].each do |handler|
26
+ handler.expects(:call).with(:test, 1).once
27
+ end
28
+ handler3.expects(:call).once
29
+ emitter.on :data, &handler1
30
+ emitter.on :data, &handler2
31
+ emitter.on :end, &handler3
32
+ emitter.send :emit, :data, :test, 1
33
+ emitter.send :emit, :end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'commute/core/util/path'
3
+
4
+ describe Commute::Path do
5
+
6
+ Path = Commute::Path
7
+
8
+ describe 'with a single route' do
9
+
10
+ it 'should walk the entire route' do
11
+ path = Path.new([1,2,3].each)
12
+ [path.succ, path.succ, path.succ].must_equal [1,2,3]
13
+ end
14
+ end
15
+
16
+ describe 'with one detour' do
17
+
18
+ it 'should walk the main route, the detour and the rest of the main route' do
19
+ router = Path.new([1,2,3].each)
20
+ router.succ.must_equal 1
21
+ router.succ.must_equal 2
22
+ router << ['a', 'b'].each
23
+ router.succ.must_equal 'a'
24
+ router.succ.must_equal 'b'
25
+ router.succ.must_equal 3
26
+ router.succ.must_equal nil
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ require 'commute/core/util/stream'
4
+
5
+ describe Commute::Stream do
6
+
7
+ let(:stream) {
8
+ Class.new { include Commute::Stream }.new
9
+ }
10
+
11
+ let(:handler) { proc {} }
12
+
13
+ describe '#new' do
14
+ it 'should be writeable' do
15
+ stream.writeable?.must_equal true
16
+ end
17
+ end
18
+
19
+ describe '#write' do
20
+ it 'should write data to the stream' do
21
+ handler.expects(:call).twice
22
+ stream.on :data, &handler
23
+ stream.write 1
24
+ stream.write 2
25
+ stream.writeable?.must_equal true
26
+ end
27
+ end
28
+
29
+ describe '#end' do
30
+ it 'should end the stream' do
31
+ data_handler = proc {}
32
+ end_handler = proc {}
33
+ stream.on :data, &data_handler
34
+ stream.on :end, &end_handler
35
+ data_handler.expects(:call).twice
36
+ end_handler.expects(:call).once
37
+ stream.write 1
38
+ stream.write 2
39
+ stream.end
40
+ stream.write(3).must_equal nil
41
+ stream.writeable?.must_equal false
42
+ end
43
+ end
44
+
45
+ describe '#buffer' do
46
+ it 'should buffer all stream data when a buffer is given' do
47
+ buffer = []
48
+ stream >> buffer
49
+ 0.upto(3) { |i| stream.write i }
50
+ stream.end
51
+ buffer.must_equal [0,1,2,3]
52
+ end
53
+
54
+ it 'should buffer all stream data when no buffer is given' do
55
+ stream.buffer do |buffer|
56
+ buffer.must_equal [0,1,2,3]
57
+ end
58
+ 0.upto(3) { |i| stream.write i }
59
+ end
60
+ end
61
+
62
+ describe '#pipe' do
63
+ it 'should pipe the stream to an other stream' do
64
+ destination = mock
65
+ streaming = sequence 'streaming'
66
+ destination.expects(:write).with(1).in_sequence streaming
67
+ destination.expects(:write).with(2).in_sequence streaming
68
+ destination.expects(:end).in_sequence streaming
69
+ stream.pipe destination
70
+ stream.write 1
71
+ stream.write 2
72
+ stream.end
73
+ end
74
+ end
75
+
76
+ describe '#map' do
77
+ it 'should map all data in a stream' do
78
+ buffer = []
79
+ output = stream.
80
+ map { |i| i*2 }.
81
+ reject { |i| i % 3 == 0 }.
82
+ inject(0, &:+) >> buffer
83
+ 1.upto(5) do |i|
84
+ stream.write i
85
+ end
86
+ stream.end
87
+ buffer.must_equal [24]
88
+ end
89
+ end
90
+ end