shift-nanite 0.4.1.2

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 (63) hide show
  1. data/LICENSE +201 -0
  2. data/README.rdoc +430 -0
  3. data/Rakefile +76 -0
  4. data/TODO +24 -0
  5. data/bin/nanite-admin +65 -0
  6. data/bin/nanite-agent +79 -0
  7. data/bin/nanite-mapper +50 -0
  8. data/lib/nanite.rb +74 -0
  9. data/lib/nanite/actor.rb +71 -0
  10. data/lib/nanite/actor_registry.rb +26 -0
  11. data/lib/nanite/admin.rb +138 -0
  12. data/lib/nanite/agent.rb +264 -0
  13. data/lib/nanite/amqp.rb +58 -0
  14. data/lib/nanite/cluster.rb +250 -0
  15. data/lib/nanite/config.rb +112 -0
  16. data/lib/nanite/console.rb +39 -0
  17. data/lib/nanite/daemonize.rb +13 -0
  18. data/lib/nanite/identity.rb +16 -0
  19. data/lib/nanite/job.rb +104 -0
  20. data/lib/nanite/local_state.rb +38 -0
  21. data/lib/nanite/log.rb +66 -0
  22. data/lib/nanite/log/formatter.rb +39 -0
  23. data/lib/nanite/mapper.rb +309 -0
  24. data/lib/nanite/mapper_proxy.rb +67 -0
  25. data/lib/nanite/nanite_dispatcher.rb +92 -0
  26. data/lib/nanite/packets.rb +365 -0
  27. data/lib/nanite/pid_file.rb +52 -0
  28. data/lib/nanite/reaper.rb +39 -0
  29. data/lib/nanite/security/cached_certificate_store_proxy.rb +24 -0
  30. data/lib/nanite/security/certificate.rb +55 -0
  31. data/lib/nanite/security/certificate_cache.rb +66 -0
  32. data/lib/nanite/security/distinguished_name.rb +34 -0
  33. data/lib/nanite/security/encrypted_document.rb +46 -0
  34. data/lib/nanite/security/rsa_key_pair.rb +53 -0
  35. data/lib/nanite/security/secure_serializer.rb +68 -0
  36. data/lib/nanite/security/signature.rb +46 -0
  37. data/lib/nanite/security/static_certificate_store.rb +35 -0
  38. data/lib/nanite/security_provider.rb +47 -0
  39. data/lib/nanite/serializer.rb +52 -0
  40. data/lib/nanite/state.rb +168 -0
  41. data/lib/nanite/streaming.rb +125 -0
  42. data/lib/nanite/util.rb +58 -0
  43. data/spec/actor_registry_spec.rb +60 -0
  44. data/spec/actor_spec.rb +77 -0
  45. data/spec/agent_spec.rb +240 -0
  46. data/spec/cached_certificate_store_proxy_spec.rb +34 -0
  47. data/spec/certificate_cache_spec.rb +49 -0
  48. data/spec/certificate_spec.rb +27 -0
  49. data/spec/cluster_spec.rb +622 -0
  50. data/spec/distinguished_name_spec.rb +24 -0
  51. data/spec/encrypted_document_spec.rb +21 -0
  52. data/spec/job_spec.rb +251 -0
  53. data/spec/local_state_spec.rb +130 -0
  54. data/spec/nanite_dispatcher_spec.rb +136 -0
  55. data/spec/packet_spec.rb +220 -0
  56. data/spec/rsa_key_pair_spec.rb +33 -0
  57. data/spec/secure_serializer_spec.rb +41 -0
  58. data/spec/serializer_spec.rb +107 -0
  59. data/spec/signature_spec.rb +30 -0
  60. data/spec/spec_helper.rb +33 -0
  61. data/spec/static_certificate_store_spec.rb +30 -0
  62. data/spec/util_spec.rb +63 -0
  63. metadata +129 -0
@@ -0,0 +1,24 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::DistinguishedName do
4
+
5
+ before(:all) do
6
+ test_dn = { 'C' => 'US',
7
+ 'ST' => 'California',
8
+ 'L' => 'Santa Barbara',
9
+ 'O' => 'RightScale',
10
+ 'OU' => 'Certification Services',
11
+ 'CN' => 'rightscale.com/emailAddress=cert@rightscale.com' }
12
+ @dn = Nanite::DistinguishedName.new(test_dn)
13
+ end
14
+
15
+ it 'should convert to string and X509 DN' do
16
+ @dn.to_s.should_not be_nil
17
+ @dn.to_x509.should_not be_nil
18
+ end
19
+
20
+ it 'should correctly encode' do
21
+ @dn.to_s.should == @dn.to_x509.to_s
22
+ end
23
+
24
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::EncryptedDocument do
4
+
5
+ include SpecHelpers
6
+
7
+ before(:all) do
8
+ @test_data = "Test Data to Sign"
9
+ @cert, @key = issue_cert
10
+ @doc = Nanite::EncryptedDocument.new(@test_data, @cert)
11
+ end
12
+
13
+ it 'should create encrypted data' do
14
+ @doc.encrypted_data.should_not be_nil
15
+ end
16
+
17
+ it 'should decrypt correctly' do
18
+ @doc.decrypted_data(@key, @cert).should == @test_data
19
+ end
20
+
21
+ end
@@ -0,0 +1,251 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::JobWarden do
4
+
5
+ describe "Creating a new Job" do
6
+
7
+ before(:each) do
8
+ @serializer = mock("Serializer")
9
+ @warden = Nanite::JobWarden.new(@serializer)
10
+
11
+ @request = mock("Request")
12
+ @targets = mock("Targets")
13
+ @job = mock("Job", :token => "3faba24fcc")
14
+ end
15
+
16
+ it "should instantiate a new Job" do
17
+ Nanite::Job.should_receive(:new).with(@request, @targets, nil, nil).and_return(@job)
18
+ @warden.new_job(@request, @targets)
19
+ end
20
+
21
+ it "should add the job to the job list" do
22
+ Nanite::Job.should_receive(:new).with(@request, @targets, nil, nil).and_return(@job)
23
+ @warden.jobs.size.should == 0
24
+ @warden.new_job(@request, @targets)
25
+ @warden.jobs.size.should == 1
26
+ @warden.jobs["3faba24fcc"].should == @job
27
+ end
28
+
29
+ it "return the newly crated job" do
30
+ Nanite::Job.should_receive(:new).with(@request, @targets, nil, nil).and_return(@job)
31
+ @warden.new_job(@request, @targets).should == @job
32
+ end
33
+
34
+ end # Creating a new Job
35
+
36
+ describe "Processing an intermediate message" do
37
+ before(:each) do
38
+ @intm_handler = lambda {|arg1, arg2, arg3| puts 'ehlo'}
39
+ @message = mock("Message", :token => "3faba24fcc", :from => 'nanite-agent')
40
+ @serializer = mock("Serializer", :load => @message)
41
+ @warden = Nanite::JobWarden.new(@serializer)
42
+ @job = Nanite::Job.new(stub("request", :token => "3faba24fcc"), [], @intm_handler)
43
+ @job.instance_variable_set(:@pending_keys, ["defaultkey"])
44
+ @job.instance_variable_set(:@intermediate_state, {"nanite-agent" => {"defaultkey" => [1]}})
45
+ @warden.jobs[@job.token] = @job
46
+ Nanite::Log.stub!(:debug)
47
+ Nanite::Log.stub!(:error)
48
+ end
49
+
50
+ it "should call the intermediate handler with three parameters" do
51
+ @intm_handler.should_receive(:call).with("defaultkey", "nanite-agent", 1)
52
+ @warden.process(@message)
53
+ end
54
+
55
+ it "should call the intermediate handler with four parameters" do
56
+ @intm_handler.stub!(:arity).and_return(4)
57
+ @intm_handler.should_receive(:call).with("defaultkey", "nanite-agent", 1, @job)
58
+ @warden.process(@message)
59
+ end
60
+
61
+ it "should not call the intermediate handler when it can't be found for the specified key" do
62
+ @intm_handler.should_not_receive(:call)
63
+ @job.instance_variable_set(:@intermediate_handler, nil)
64
+ @warden.process(@message)
65
+ end
66
+
67
+ it "should call the intermediate handler with one parameter which needs to be the result" do
68
+ @intm_handler.should_receive(:call).with(1, @job)
69
+ @intm_handler.stub!(:arity).and_return(2)
70
+ @warden.process(@message)
71
+ end
72
+ end
73
+
74
+ describe "Processing a Message" do
75
+
76
+ before(:each) do
77
+ @message = mock("Message", :token => "3faba24fcc")
78
+ @warden = Nanite::JobWarden.new(@serializer)
79
+ @job = mock("Job", :token => "3faba24fcc", :process => true, :completed? => false, :results => 42, :pending_keys => [], :intermediate_handler => true)
80
+
81
+ Nanite::Log.stub!(:debug)
82
+ end
83
+
84
+ it "should hand over processing to job" do
85
+ Nanite::Job.stub!(:new).and_return(@job)
86
+ @job.should_receive(:process).with(@message)
87
+
88
+ @warden.new_job("request", "targets")
89
+ @warden.process(@message)
90
+ end
91
+
92
+ it "should delete job from jobs after completion" do
93
+ Nanite::Job.stub!(:new).and_return(@job)
94
+ @job.should_receive(:process).with(@message)
95
+ @job.should_receive(:completed?).and_return(true)
96
+ @job.should_receive(:completed).and_return(nil)
97
+
98
+ @warden.jobs["3faba24fcc"].should be_nil
99
+ @warden.new_job("request", "targets")
100
+ @warden.jobs["3faba24fcc"].should == @job
101
+ @warden.process(@message)
102
+ @warden.jobs["3faba24fcc"].should be_nil
103
+ end
104
+
105
+ it "should call completed block after completion" do
106
+ completed_block = mock("Completed", :arity => 1, :call => true)
107
+
108
+ Nanite::Job.stub!(:new).and_return(@job)
109
+ @job.should_receive(:process).with(@message)
110
+ @job.should_receive(:completed?).and_return(true)
111
+ @job.should_receive(:completed).exactly(3).times.and_return(completed_block)
112
+
113
+ @warden.new_job("request", "targets")
114
+ @warden.process(@message)
115
+ end
116
+
117
+ it "should pass in job result if arity of completed block is one" do
118
+ completed_block = mock("Completed")
119
+
120
+ Nanite::Job.stub!(:new).and_return(@job)
121
+ @job.should_receive(:process).with(@message)
122
+ @job.should_receive(:completed?).and_return(true)
123
+ @job.should_receive(:completed).exactly(3).times.and_return(completed_block)
124
+ @job.should_receive(:results).and_return("the job result")
125
+ completed_block.should_receive(:arity).and_return(1)
126
+ completed_block.should_receive(:call).with("the job result")
127
+
128
+ @warden.new_job("request", "targets")
129
+ @warden.process(@message)
130
+ end
131
+
132
+ it "should pass in job result and job if arity of completed block is two" do
133
+ completed_block = mock("Completed")
134
+
135
+ Nanite::Job.stub!(:new).and_return(@job)
136
+ @job.should_receive(:process).with(@message)
137
+ @job.should_receive(:completed?).and_return(true)
138
+ @job.should_receive(:completed).exactly(3).times.and_return(completed_block)
139
+ @job.should_receive(:results).and_return("the job result")
140
+ completed_block.should_receive(:arity).and_return(2)
141
+ completed_block.should_receive(:call).with("the job result", @job)
142
+
143
+ @warden.new_job("request", "targets")
144
+ @warden.process(@message)
145
+ end
146
+
147
+ end # Processing a Message
148
+
149
+ end # Nanite::JobWarden
150
+
151
+
152
+ describe Nanite::Job do
153
+
154
+ describe "Creating a Job" do
155
+
156
+ before(:each) do
157
+ @request = mock("Request", :token => "af534ceaaacdcd")
158
+ end
159
+
160
+ it "should initialize the request" do
161
+ job = Nanite::Job.new(@request, nil, nil)
162
+ job.request.should == @request
163
+ end
164
+
165
+ it "should initialize the targets" do
166
+ job = Nanite::Job.new(@request, "targets", nil)
167
+ job.targets.should == "targets"
168
+ end
169
+
170
+ it "should initialize the job token to the request token" do
171
+ job = Nanite::Job.new(@request, nil, nil)
172
+ job.token.should == "af534ceaaacdcd"
173
+ end
174
+
175
+ it "should initialize the results to an empty hash" do
176
+ job = Nanite::Job.new(@request, nil, nil)
177
+ job.results.should == {}
178
+ end
179
+
180
+ it "should initialize the intermediate state to an empty hash" do
181
+ job = Nanite::Job.new(@request, nil, nil)
182
+ job.intermediate_state.should == {}
183
+ end
184
+
185
+ it "should initialize the job block" do
186
+ job = Nanite::Job.new(@request, nil, nil, "my block")
187
+ job.completed.should == "my block"
188
+ end
189
+
190
+ end # Creating a new Job
191
+
192
+
193
+ describe "Processing a Message" do
194
+
195
+ before(:each) do
196
+ @request = mock("Request", :token => "feeefe132")
197
+ end
198
+
199
+ it "should set the job result (for sender) to the message result for 'final' status messages" do
200
+ job = Nanite::Job.new(@request, [], nil)
201
+ message = Nanite::Result.new('token', 'to', 'results', 'from')
202
+ job.results.should == {}
203
+ job.process(message)
204
+ job.results.should == { 'from' => 'results' }
205
+ end
206
+
207
+ it "should delete the message sender from the targets for 'final' status messages" do
208
+ job = Nanite::Job.new(@request, ['from'], nil)
209
+ message = Nanite::Result.new('token', 'to', 'results', 'from')
210
+ job.targets.should == ['from']
211
+ job.process(message)
212
+ job.targets.should == []
213
+ end
214
+
215
+ it "should set the job result (for sender) to the message result for 'intermediate' status messages" do
216
+ job = Nanite::Job.new(@request, ['from'], nil)
217
+ message = Nanite::IntermediateMessage.new('token', 'to', 'from', 'messagekey', 'message')
218
+ job.process(message)
219
+ job.intermediate_state.should == { 'from' => { 'messagekey' => ['message'] } }
220
+ end
221
+
222
+ it "should not delete the message sender from the targets for 'intermediate' status messages" do
223
+ job = Nanite::Job.new(@request, ['from'], nil)
224
+ message = Nanite::IntermediateMessage.new('token', 'to', 'from', 'messagekey', 'message')
225
+ job.targets.should == ['from']
226
+ job.process(message)
227
+ job.targets.should == ['from']
228
+ end
229
+
230
+ end # Processing a Message
231
+
232
+
233
+ describe "Completion" do
234
+
235
+ before(:each) do
236
+ @request = mock("Request", :token => "af534ceaaacdcd")
237
+ end
238
+
239
+ it "should be true is targets are empty" do
240
+ job = Nanite::Job.new(@request, {}, nil)
241
+ job.completed?.should == true
242
+ end
243
+
244
+ it "should be false is targets are not empty" do
245
+ job = Nanite::Job.new(@request, { :a => 1 }, nil)
246
+ job.completed?.should == false
247
+ end
248
+
249
+ end # Completion
250
+
251
+ end # Nanite::Job
@@ -0,0 +1,130 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'nanite/local_state'
3
+
4
+ describe "Nanite::LocalState: " do
5
+
6
+ describe "Class" do
7
+
8
+ it "should a Hash" do
9
+ Nanite::LocalState.new({}).should be_kind_of(Hash)
10
+ end
11
+
12
+ it "should create empty hash if no hash passed in" do
13
+ Nanite::LocalState.new.should == {}
14
+ end
15
+
16
+ it "should initialize hash with value passed in" do
17
+ state = Nanite::LocalState.new({:a => 1, :b => 2, :c => 3})
18
+ state.should == {:a => 1, :b => 2, :c => 3}
19
+ end
20
+
21
+ end # Class
22
+
23
+
24
+ describe "All services" do
25
+
26
+ it "should return empty array if no services are defined" do
27
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :bar => 2 }})
28
+ state.all_services.should == []
29
+ end
30
+
31
+ it "should return all :services values" do
32
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :services => "b's services" }, :c => { :services => "c's services" }})
33
+ state.all_services.should include("b's services")
34
+ state.all_services.should include("c's services")
35
+ end
36
+
37
+ it "should only return one entry for each service" do
38
+ state = Nanite::LocalState.new({:f => { :services => "services" }, :b => { :services => "services" }})
39
+ state.all_services.size == 1
40
+ state.all_services.should == ["services"]
41
+ end
42
+
43
+ end # All services
44
+
45
+
46
+ describe "All tags" do
47
+
48
+ it "should return empty array if no tags are defined" do
49
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :bar => 2 }})
50
+ state.all_tags.should == []
51
+ end
52
+
53
+ it "should return all :tags values" do
54
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :tags => ["a", "b"] }, :c => { :tags => ["c", "d"] }})
55
+ state.all_tags.should include("a")
56
+ state.all_tags.should include("b")
57
+ state.all_tags.should include("c")
58
+ state.all_tags.should include("d")
59
+ end
60
+
61
+ it "should only return one entry for each tag" do
62
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :tags => ["a", "b"] }, :c => { :tags => ["a", "c"] }})
63
+ state.all_tags.size == 3
64
+ state.all_tags.should include("a")
65
+ state.all_tags.should include("b")
66
+ state.all_tags.should include("c")
67
+ end
68
+
69
+ end # All tags
70
+
71
+
72
+ describe "Nanites lookup" do
73
+
74
+ it "should find services matching the service criteria if no tags criteria is specified" do
75
+ state = Nanite::LocalState.new({:a => { :services => "a's services" }, :b => { :services => "b's services" }})
76
+ state.nanites_for("b's services").should == [[:b, {:services => "b's services"}]]
77
+ end
78
+
79
+ it "should find all services matching the service criteria if no tags criteria is specified" do
80
+ state = Nanite::LocalState.new({:a => { :services => "services" }, :b => { :services => "services" }, :c => { :services => "other services" }})
81
+ state.nanites_for("services").should include([:a, {:services => "services"}])
82
+ state.nanites_for("services").should include([:b, {:services => "services"}])
83
+ end
84
+
85
+ it "should only services matching the service criteria that also match the tags criteria" do
86
+ state = Nanite::LocalState.new({:a => { :services => "a's services", :tags => ["a_1", "a_2"] }, :b => { :services => "b's services", :tags => ["b_1", "b_2"] }})
87
+ state.nanites_for("b's services").should == [[:b, {:tags=>["b_1", "b_2"], :services=>"b's services"}]]
88
+ end
89
+
90
+ it "should find all services with matching tags even if the tag order is different" do
91
+ state = Nanite::LocalState.new({:a => { :services => "services", :tags => ["a_1", "a_2"] }, :b => { :services => "services", :tags => ["a_2", "a_1"] }})
92
+ state.nanites_for("services", ['a_1', 'a_2']).should == [[:a, {:tags=>["a_1", "a_2"], :services=>"services"}], [:b, {:tags=>["a_2", "a_1"], :services=>"services"}]]
93
+ end
94
+
95
+ it "should also return all tags for services matching the service criteria that also match a single tags criterium" do
96
+ state = Nanite::LocalState.new({:a => { :services => "services", :tags => ["t_1", "t_2"] }})
97
+ state.nanites_for("services", ["t_1"]).should == [[:a, {:tags=>["t_1", "t_2"], :services=>"services"}]]
98
+ end
99
+
100
+ it "should return services matching the service criteria and also match the tags criterium" do
101
+ state = Nanite::LocalState.new({:a => { :services => "a's services", :tags => ["a_1", "a_2"] }, :b => { :services => "b's services", :tags => ["b_1", "b_2"] }})
102
+ state.nanites_for("b's services", ["b_1"]).should == [[:b, {:tags=>["b_1", "b_2"], :services=>"b's services"}]]
103
+ end
104
+
105
+ it "should ignore services matching the service criteria and but not the tags criteria" do
106
+ state = Nanite::LocalState.new({:a => { :services => "services", :tags => ["t_1", "t_2"] }, :b => { :services => "services", :tags => ["t_3", "t_4"] }})
107
+ state.nanites_for("services", ["t_1"]).should == [[:a, {:services => "services", :tags => ["t_1", "t_2"]}]]
108
+ end
109
+
110
+ it "should lookup services matching the service criteria and and any of the tags criteria" do
111
+ state = Nanite::LocalState.new({'a' => { :services => "services", :tags => ["t_1", "t_2"] }, 'b' => { :services => "services", :tags => ["t_2", "t_3"] }})
112
+ state.nanites_for("services", ["t_1", "t_3"]).sort.should == [['a', {:services => "services", :tags => ["t_1", "t_2"]}], ['b', {:services => "services", :tags => ["t_2", "t_3"]}]]
113
+ end
114
+
115
+ end # Nanites lookup
116
+
117
+ describe "Updating a Nanite's status" do
118
+ it "should set the status for the nanite" do
119
+ state = Nanite::LocalState.new('a' => { :services => "service" })
120
+ state.update_status('a', 0.1)
121
+ state['a'][:status].should == 0.1
122
+ end
123
+
124
+ it "should store the timestamp for the nanite" do
125
+ state = Nanite::LocalState.new('a' => { :services => "service" })
126
+ state.update_status('a', 0.1)
127
+ state['a'][:timestamp].should be_close(Time.now.utc.to_i, 1)
128
+ end
129
+ end
130
+ end # Nanite::LocalState
@@ -0,0 +1,136 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ class Foo
4
+ include Nanite::Actor
5
+ expose :bar, :index, :i_kill_you
6
+ on_exception :handle_exception
7
+
8
+ def index(payload)
9
+ bar(payload)
10
+ end
11
+
12
+ def bar(payload)
13
+ ['hello', payload]
14
+ end
15
+
16
+ def bar2(payload, deliverable)
17
+ deliverable
18
+ end
19
+
20
+ def i_kill_you(payload)
21
+ raise RuntimeError.new('I kill you!')
22
+ end
23
+
24
+ def handle_exception(method, deliverable, error)
25
+ end
26
+ end
27
+
28
+ class Bar
29
+ include Nanite::Actor
30
+ expose :i_kill_you
31
+ on_exception do |method, deliverable, error|
32
+ @scope = self
33
+ @called_with = [method, deliverable, error]
34
+ end
35
+
36
+ def i_kill_you(payload)
37
+ raise RuntimeError.new('I kill you!')
38
+ end
39
+ end
40
+
41
+ # No specs, simply ensures multiple methods for assigning on_exception callback,
42
+ # on_exception raises exception when called with an invalid argument.
43
+ class Doomed
44
+ include Nanite::Actor
45
+ on_exception do
46
+ end
47
+ on_exception lambda {}
48
+ on_exception :doh
49
+ end
50
+
51
+ # Mock the EventMachine deferrer.
52
+ class EMMock
53
+ def self.defer(op = nil, callback = nil)
54
+ callback.call(op.call)
55
+ end
56
+ end
57
+
58
+ describe "Nanite::Dispatcher" do
59
+
60
+ before(:each) do
61
+ Nanite::Log.stub!(:info)
62
+ Nanite::Log.stub!(:error)
63
+ amq = mock('amq', :queue => mock('queue', :publish => nil))
64
+ @actor = Foo.new
65
+ @registry = Nanite::ActorRegistry.new
66
+ @registry.register(@actor, nil)
67
+ @dispatcher = Nanite::Dispatcher.new(amq, @registry, Nanite::Serializer.new(:marshal), '0xfunkymonkey', {})
68
+ @dispatcher.evmclass = EMMock
69
+ end
70
+
71
+ it "should dispatch a request" do
72
+ req = Nanite::Request.new('/foo/bar', 'you')
73
+ res = @dispatcher.dispatch(req)
74
+ res.should(be_kind_of(Nanite::Result))
75
+ res.token.should == req.token
76
+ res.results.should == ['hello', 'you']
77
+ end
78
+
79
+ it "should dispatch the deliverable to actions that accept it" do
80
+ req = Nanite::Request.new('/foo/bar2', 'you')
81
+ res = @dispatcher.dispatch(req)
82
+ res.should(be_kind_of(Nanite::Result))
83
+ res.token.should == req.token
84
+ res.results.should == req
85
+ end
86
+
87
+ it "should dispatch a request to the default action" do
88
+ req = Nanite::Request.new('/foo', 'you')
89
+ res = @dispatcher.dispatch(req)
90
+ res.should(be_kind_of(Nanite::Result))
91
+ res.token.should == req.token
92
+ res.results.should == ['hello', 'you']
93
+ end
94
+
95
+ it "should handle custom prefixes" do
96
+ @registry.register(Foo.new, 'umbongo')
97
+ req = Nanite::Request.new('/umbongo/bar', 'you')
98
+ res = @dispatcher.dispatch(req)
99
+ res.should(be_kind_of(Nanite::Result))
100
+ res.token.should == req.token
101
+ res.results.should == ['hello', 'you']
102
+ end
103
+
104
+ it "should call the on_exception callback if something goes wrong" do
105
+ req = Nanite::Request.new('/foo/i_kill_you', nil)
106
+ @actor.should_receive(:handle_exception).with(:i_kill_you, req, duck_type(:exception, :backtrace))
107
+ @dispatcher.dispatch(req)
108
+ end
109
+
110
+ it "should call on_exception Procs defined in a subclass with the correct arguments" do
111
+ actor = Bar.new
112
+ @registry.register(actor, nil)
113
+ req = Nanite::Request.new('/bar/i_kill_you', nil)
114
+ @dispatcher.dispatch(req)
115
+ called_with = actor.instance_variable_get("@called_with")
116
+ called_with[0].should == :i_kill_you
117
+ called_with[1].should == req
118
+ called_with[2].should be_kind_of(RuntimeError)
119
+ called_with[2].message.should == 'I kill you!'
120
+ end
121
+
122
+ it "should call on_exception Procs defined in a subclass in the scope of the actor" do
123
+ actor = Bar.new
124
+ @registry.register(actor, nil)
125
+ req = Nanite::Request.new('/bar/i_kill_you', nil)
126
+ @dispatcher.dispatch(req)
127
+ actor.instance_variable_get("@scope").should == actor
128
+ end
129
+
130
+ it "should log error if something goes wrong" do
131
+ Nanite::Log.should_receive(:error)
132
+ req = Nanite::Request.new('/foo/i_kill_you', nil)
133
+ @dispatcher.dispatch(req)
134
+ end
135
+
136
+ end # Nanite::Dispatcher