bumbleworks 0.0.48 → 0.0.50

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 (39) hide show
  1. data/.gitignore +4 -1
  2. data/lib/bumbleworks.rb +9 -8
  3. data/lib/bumbleworks/configuration.rb +8 -5
  4. data/lib/bumbleworks/entity.rb +88 -0
  5. data/lib/bumbleworks/participant.rb +6 -7
  6. data/lib/bumbleworks/participant/base.rb +11 -0
  7. data/lib/bumbleworks/participant/entity_interactor.rb +29 -0
  8. data/lib/bumbleworks/participant/error_handler.rb +14 -0
  9. data/lib/bumbleworks/participant/local_participant.rb +11 -0
  10. data/lib/bumbleworks/participant/storage_participant.rb +11 -0
  11. data/lib/bumbleworks/process.rb +64 -0
  12. data/lib/bumbleworks/ruote.rb +12 -4
  13. data/lib/bumbleworks/support.rb +3 -5
  14. data/lib/bumbleworks/task.rb +6 -4
  15. data/lib/bumbleworks/{tasks → task}/base.rb +1 -1
  16. data/lib/bumbleworks/task/finder.rb +39 -3
  17. data/lib/bumbleworks/version.rb +1 -1
  18. data/spec/fixtures/entities/furby.rb +30 -0
  19. data/spec/integration/entity_spec.rb +49 -0
  20. data/spec/integration/history_storage_spec.rb +4 -4
  21. data/spec/integration/sample_application_spec.rb +8 -2
  22. data/spec/lib/bumbleworks/entity_spec.rb +208 -0
  23. data/spec/lib/bumbleworks/{participant_spec.rb → participant/base_spec.rb} +3 -3
  24. data/spec/lib/bumbleworks/participant/entity_interactor_spec.rb +91 -0
  25. data/spec/lib/bumbleworks/{error_handler_participant_spec.rb → participant/error_handler_spec.rb} +1 -1
  26. data/spec/lib/bumbleworks/{local_participant_spec.rb → participant/local_participant_spec.rb} +1 -1
  27. data/spec/lib/bumbleworks/process_spec.rb +147 -0
  28. data/spec/lib/bumbleworks/ruote/exp/broadcast_event_expression_spec.rb +1 -1
  29. data/spec/lib/bumbleworks/ruote/exp/wait_for_event_expression_spec.rb +3 -3
  30. data/spec/lib/bumbleworks/ruote_spec.rb +28 -21
  31. data/spec/lib/bumbleworks/task/finder_spec.rb +9 -0
  32. data/spec/lib/bumbleworks/task_spec.rb +98 -3
  33. data/spec/lib/bumbleworks_spec.rb +14 -7
  34. data/spec/spec_helper.rb +0 -1
  35. data/spec/support/process_testing_helpers.rb +9 -0
  36. metadata +34 -13
  37. data/lib/bumbleworks/error_handler_participant.rb +0 -12
  38. data/lib/bumbleworks/local_participant.rb +0 -9
  39. data/lib/bumbleworks/storage_participant.rb +0 -9
@@ -1,4 +1,4 @@
1
- describe Bumbleworks::LocalParticipant do
1
+ describe Bumbleworks::Participant::LocalParticipant do
2
2
  it 'includes Ruote::LocalParticipant' do
3
3
  described_class.included_modules.should include(Ruote::LocalParticipant)
4
4
  end
@@ -0,0 +1,147 @@
1
+ describe Bumbleworks::Process do
2
+ before :each do
3
+ Bumbleworks.reset!
4
+ Bumbleworks.storage = {}
5
+ Bumbleworks::Ruote.register_participants
6
+ Bumbleworks.start_worker!
7
+
8
+ Bumbleworks.define_process 'going_to_the_dance' do
9
+ concurrence do
10
+ wait_for_event :an_invitation
11
+ await :left_tag => 'a_friend'
12
+ end
13
+ end
14
+ Bumbleworks.define_process 'straightening_the_rocks' do
15
+ concurrence do
16
+ wait_for_event :rock_caliper_delivery
17
+ wait_for_event :speedos
18
+ end
19
+ end
20
+ end
21
+
22
+ describe '.new' do
23
+ it 'sets workflow id' do
24
+ bp = described_class.new('apples')
25
+ bp.id.should == 'apples'
26
+ end
27
+ end
28
+
29
+ describe '#wfid' do
30
+ it 'is aliased to id' do
31
+ bp = described_class.new('smorgatoof')
32
+ bp.wfid.should == 'smorgatoof'
33
+ end
34
+ end
35
+
36
+ describe '#tasks' do
37
+ it 'returns task query filtered for this process' do
38
+ bp = described_class.new('chumpy')
39
+ Bumbleworks::Task.stub(:for_process).with('chumpy').and_return(:my_task_query)
40
+ bp.tasks.should == :my_task_query
41
+ end
42
+ end
43
+
44
+ describe '#trackers' do
45
+ it 'lists all trackers this process is waiting on' do
46
+ bp1 = Bumbleworks.launch!('going_to_the_dance')
47
+ bp2 = Bumbleworks.launch!('straightening_the_rocks')
48
+ wait_until { bp1.trackers.count == 2 && bp2.trackers.count == 2 }
49
+ bp1.trackers.map { |t| t['msg']['fei']['wfid'] }.should == [bp1.wfid, bp1.wfid]
50
+ bp2.trackers.map { |t| t['msg']['fei']['wfid'] }.should == [bp2.wfid, bp2.wfid]
51
+ bp1.trackers.map { |t| t['action'] }.should == ['left_tag', 'left_tag']
52
+ bp2.trackers.map { |t| t['action'] }.should == ['left_tag', 'left_tag']
53
+ bp1.trackers.map { |t| t['conditions']['tag'] }.should == [['an_invitation'], ['a_friend']]
54
+ bp2.trackers.map { |t| t['conditions']['tag'] }.should == [['rock_caliper_delivery'], ['speedos']]
55
+ end
56
+ end
57
+
58
+ describe '#all_subscribed_tags' do
59
+ it 'lists all tags this process is waiting on' do
60
+ bp1 = Bumbleworks.launch!('going_to_the_dance')
61
+ bp2 = Bumbleworks.launch!('straightening_the_rocks')
62
+ wait_until { bp1.trackers.count == 2 && bp2.trackers.count == 2 }
63
+ bp1.all_subscribed_tags.should == { :global => ['an_invitation'], bp1.wfid => ['a_friend'] }
64
+ bp2.all_subscribed_tags.should == { :global => ['rock_caliper_delivery', 'speedos'] }
65
+ end
66
+ end
67
+
68
+ describe '#subscribed_events' do
69
+ it 'lists all events (global tags) this process is waiting on' do
70
+ bp1 = Bumbleworks.launch!('going_to_the_dance')
71
+ bp2 = Bumbleworks.launch!('straightening_the_rocks')
72
+ wait_until { bp1.trackers.count == 2 && bp2.trackers.count == 2 }
73
+ bp1.subscribed_events.should == ['an_invitation']
74
+ bp2.subscribed_events.should == ['rock_caliper_delivery', 'speedos']
75
+ end
76
+ end
77
+
78
+ describe '#is_waiting_for?' do
79
+ it 'returns true if event is in subscribed events' do
80
+ bp = described_class.new('whatever')
81
+ bp.stub(:subscribed_events => ['ghosts', 'mouses'])
82
+ bp.is_waiting_for?('mouses').should be_true
83
+ end
84
+
85
+ it 'converts symbolized queries' do
86
+ bp = described_class.new('whatever')
87
+ bp.stub(:subscribed_events => ['ghosts', 'mouses'])
88
+ bp.is_waiting_for?(:ghosts).should be_true
89
+ end
90
+
91
+ it 'returns false if event is not in subscribed events' do
92
+ bp = described_class.new('whatever')
93
+ bp.stub(:subscribed_events => ['ghosts', 'mouses'])
94
+ bp.is_waiting_for?('organs').should be_false
95
+ end
96
+ end
97
+
98
+ describe '#kill!' do
99
+ it 'kills process' do
100
+ bp = described_class.new('frogheads')
101
+ Bumbleworks.should_receive(:kill_process!).with('frogheads')
102
+ bp.kill!
103
+ end
104
+ end
105
+
106
+ describe '#cancel!' do
107
+ it 'cancels process' do
108
+ bp = described_class.new('frogheads')
109
+ Bumbleworks.should_receive(:cancel_process!).with('frogheads')
110
+ bp.cancel!
111
+ end
112
+ end
113
+
114
+ describe '#==' do
115
+ it 'returns true if other object has same wfid' do
116
+ bp1 = described_class.new('in_da_sky')
117
+ bp2 = described_class.new('in_da_sky')
118
+ bp1.should == bp2
119
+ end
120
+ end
121
+
122
+ describe '#process_status' do
123
+ it 'returns a process_status instance for the wfid' do
124
+ bp = described_class.new('frogheads')
125
+ Bumbleworks.dashboard.stub(:process).with('frogheads').and_return(:the_status)
126
+ bp.process_status.should == :the_status
127
+ end
128
+ end
129
+
130
+ describe '#method_missing' do
131
+ it 'calls method on object returned by #process_status' do
132
+ ps = double('process_status')
133
+ ps.stub(:nuffle).with(:yay).and_return(:its_a_me)
134
+ bp = described_class.new('frogheads')
135
+ bp.stub(:process_status => ps)
136
+ bp.nuffle(:yay).should == :its_a_me
137
+ end
138
+
139
+ it 'falls back to method missing if no process status method' do
140
+ bp = described_class.new('blah')
141
+ bp.stub(:process_status => double('process status'))
142
+ expect {
143
+ bp.kerplunk!(:oh_no)
144
+ }.to raise_error
145
+ end
146
+ end
147
+ end
@@ -18,7 +18,7 @@ describe Ruote::Exp::BroadcastEventExpression do
18
18
 
19
19
  waiter = Bumbleworks.launch!('waiter')
20
20
  sender = Bumbleworks.launch!('sender')
21
- Bumbleworks.dashboard.wait_for(waiter)
21
+ Bumbleworks.dashboard.wait_for(waiter.wfid)
22
22
  @tracer.should == ['amazing']
23
23
  end
24
24
  end
@@ -33,7 +33,7 @@ describe Ruote::Exp::WaitForEventExpression do
33
33
 
34
34
  waiter = Bumbleworks.launch!('waiter')
35
35
  sender = Bumbleworks.launch!('sender')
36
- Bumbleworks.dashboard.wait_for(waiter)
36
+ Bumbleworks.dashboard.wait_for(waiter.wfid)
37
37
  @tracer.should == ['get ready', 'oh my gosh almost there', 'hello', 'yay', 'yay2']
38
38
  end
39
39
 
@@ -53,7 +53,7 @@ describe Ruote::Exp::WaitForEventExpression do
53
53
  sender1 = Bumbleworks.launch!('sender', 'not_special' => 1)
54
54
  sender2 = Bumbleworks.launch!('sender', 'special' => 3)
55
55
 
56
- Bumbleworks.dashboard.wait_for(waiter3)
56
+ Bumbleworks.dashboard.wait_for(waiter3.wfid)
57
57
  @tracer.should == ['specials! 3']
58
58
  end
59
59
 
@@ -73,7 +73,7 @@ describe Ruote::Exp::WaitForEventExpression do
73
73
  sender1 = Bumbleworks.launch!('sender', 'entity_type' => 'Pigeon', 'entity_id' => '13-6zoop')
74
74
  sender2 = Bumbleworks.launch!('sender', 'entity_type' => 'Rhubarb', 'entity_id' => 'spitpickle-4boof')
75
75
 
76
- Bumbleworks.dashboard.wait_for(waiter3)
76
+ Bumbleworks.dashboard.wait_for(waiter3.wfid)
77
77
  @tracer.should == ['entities! Rhubarb,spitpickle-4boof']
78
78
  end
79
79
  end
@@ -13,11 +13,11 @@ describe Bumbleworks::Ruote do
13
13
  Bumbleworks.define_process 'do_nothing' do
14
14
  lazy_guy :task => 'absolutely_nothing'
15
15
  end
16
- wfid = Bumbleworks.launch!('do_nothing')
16
+ process = Bumbleworks.launch!('do_nothing')
17
17
  Bumbleworks.dashboard.wait_for(:lazy_guy)
18
- Bumbleworks.dashboard.process(wfid).should_not be_nil
19
- described_class.cancel_process!(wfid)
20
- Bumbleworks.dashboard.process(wfid).should be_nil
18
+ Bumbleworks.dashboard.process(process.wfid).should_not be_nil
19
+ described_class.cancel_process!(process.wfid)
20
+ Bumbleworks.dashboard.process(process.wfid).should be_nil
21
21
  end
22
22
 
23
23
  it 'times out if process is not cancelled in time' do
@@ -29,11 +29,11 @@ describe Bumbleworks::Ruote do
29
29
  wait '1s'
30
30
  end
31
31
  end
32
- wfid = Bumbleworks.launch!('time_hog')
32
+ process = Bumbleworks.launch!('time_hog')
33
33
  Bumbleworks.dashboard.wait_for(:pigheaded)
34
- Bumbleworks.dashboard.process(wfid).should_not be_nil
34
+ Bumbleworks.dashboard.process(process.wfid).should_not be_nil
35
35
  expect {
36
- described_class.cancel_process!(wfid, :timeout => 0.5)
36
+ described_class.cancel_process!(process.wfid, :timeout => 0.5)
37
37
  }.to raise_error(Bumbleworks::Ruote::CancelTimeout)
38
38
  end
39
39
  end
@@ -52,11 +52,11 @@ describe Bumbleworks::Ruote do
52
52
  wait '10s'
53
53
  end
54
54
  end
55
- wfid = Bumbleworks.launch!('do_nothing')
55
+ process = Bumbleworks.launch!('do_nothing')
56
56
  Bumbleworks.dashboard.wait_for(:lazy_guy)
57
- Bumbleworks.dashboard.process(wfid).should_not be_nil
58
- described_class.kill_process!(wfid)
59
- Bumbleworks.dashboard.process(wfid).should be_nil
57
+ Bumbleworks.dashboard.process(process.wfid).should_not be_nil
58
+ described_class.kill_process!(process.wfid)
59
+ Bumbleworks.dashboard.process(process.wfid).should be_nil
60
60
  end
61
61
 
62
62
  it 'times out if process is not killed in time' do
@@ -235,7 +235,10 @@ describe Bumbleworks::Ruote do
235
235
  described_class.register_participants
236
236
  described_class.start_worker!
237
237
  described_class.dashboard.participant_list.map(&:classname).should == [
238
- 'Bumbleworks::ErrorHandlerParticipant', 'Bumbleworks::StorageParticipant']
238
+ 'Bumbleworks::Participant::ErrorHandler',
239
+ 'Bumbleworks::Participant::EntityInteractor',
240
+ 'Bumbleworks::Participant::StorageParticipant'
241
+ ]
239
242
  Bumbleworks.dashboard.on_error.flatten[2].should == 'error_handler_participant'
240
243
  end
241
244
  end
@@ -279,9 +282,13 @@ describe Bumbleworks::Ruote do
279
282
 
280
283
  described_class.dashboard.participant_list.should be_empty
281
284
  described_class.register_participants &registration_block
282
- described_class.dashboard.participant_list.should have(5).items
285
+ described_class.dashboard.participant_list.should have(6).items
283
286
  described_class.dashboard.participant_list.map(&:classname).should == [
284
- 'Bumbleworks::ErrorHandlerParticipant', 'BeesHoney', 'MapleSyrup', 'NewCatchall', 'Bumbleworks::StorageParticipant']
287
+ 'Bumbleworks::Participant::ErrorHandler',
288
+ 'Bumbleworks::Participant::EntityInteractor',
289
+ 'BeesHoney', 'MapleSyrup', 'NewCatchall',
290
+ 'Bumbleworks::Participant::StorageParticipant'
291
+ ]
285
292
  end
286
293
 
287
294
  it 'does not add storage participant catchall if already exists' do
@@ -292,25 +299,25 @@ describe Bumbleworks::Ruote do
292
299
 
293
300
  described_class.dashboard.participant_list.should be_empty
294
301
  described_class.register_participants &registration_block
295
- described_class.dashboard.participant_list.should have(3).items
302
+ described_class.dashboard.participant_list.should have(4).items
296
303
  described_class.dashboard.participant_list.map(&:classname).should == [
297
- 'Bumbleworks::ErrorHandlerParticipant', 'BeesHoney', 'Ruote::StorageParticipant'
304
+ 'Bumbleworks::Participant::ErrorHandler', 'Bumbleworks::Participant::EntityInteractor', 'BeesHoney', 'Ruote::StorageParticipant'
298
305
  ]
299
306
  end
300
307
 
301
308
  it 'adds catchall and error_handler participants if block is nil' do
302
309
  described_class.dashboard.participant_list.should be_empty
303
310
  described_class.register_participants &nil
304
- described_class.dashboard.participant_list.should have(2).item
311
+ described_class.dashboard.participant_list.should have(3).item
305
312
  described_class.dashboard.participant_list.map(&:classname).should ==
306
- ['Bumbleworks::ErrorHandlerParticipant', 'Bumbleworks::StorageParticipant']
313
+ ['Bumbleworks::Participant::ErrorHandler', 'Bumbleworks::Participant::EntityInteractor', 'Bumbleworks::Participant::StorageParticipant']
307
314
  end
308
315
  end
309
316
 
310
317
  describe '.register_error_handler', dev:true do
311
318
  it 'registers the error handler participant' do
312
319
  described_class.register_error_handler
313
- Bumbleworks.dashboard.participant_list.map(&:classname).should include('Bumbleworks::ErrorHandlerParticipant')
320
+ Bumbleworks.dashboard.participant_list.map(&:classname).should include('Bumbleworks::Participant::ErrorHandler')
314
321
  end
315
322
 
316
323
  it 'it sets the global Ruote on_error to the error_handler_participant' do
@@ -324,7 +331,7 @@ describe Bumbleworks::Ruote do
324
331
  end
325
332
  described_class.register_error_handler
326
333
  Bumbleworks.dashboard.participant_list.map(&:classname).should ==
327
- ['Whatever', 'Bumbleworks::StorageParticipant']
334
+ ['Bumbleworks::Participant::EntityInteractor', 'Whatever', 'Bumbleworks::Participant::StorageParticipant']
328
335
 
329
336
  end
330
337
  end
@@ -362,7 +369,7 @@ describe Bumbleworks::Ruote do
362
369
  described_class.dashboard.participant_list.should be_empty
363
370
  described_class.launch('foo')
364
371
  described_class.dashboard.participant_list.should have(1).item
365
- described_class.dashboard.participant_list.first.classname.should == 'Bumbleworks::StorageParticipant'
372
+ described_class.dashboard.participant_list.first.classname.should == 'Bumbleworks::Participant::StorageParticipant'
366
373
  end
367
374
  end
368
375
 
@@ -31,4 +31,13 @@ describe Bumbleworks::Task::Finder do
31
31
  Object.send(:remove_const, :MyOwnTask)
32
32
  end
33
33
  end
34
+
35
+ describe '#available' do
36
+ it 'adds both unclaimed and completable filters' do
37
+ query = Bumbleworks::Task::Finder.new
38
+ query.should_receive(:unclaimed).and_return(query)
39
+ query.should_receive(:completable).and_return(query)
40
+ query.available
41
+ end
42
+ end
34
43
  end
@@ -131,7 +131,7 @@ describe Bumbleworks::Task do
131
131
  it 'extends with base module and task module' do
132
132
  task = described_class.new(workflow_item)
133
133
  task.should_receive(:task_module).and_return(:task_module_double)
134
- task.should_receive(:extend).with(Bumbleworks::Tasks::Base).ordered
134
+ task.should_receive(:extend).with(Bumbleworks::Task::Base).ordered
135
135
  task.should_receive(:extend).with(:task_module_double).ordered
136
136
  task.extend_module
137
137
  end
@@ -139,13 +139,13 @@ describe Bumbleworks::Task do
139
139
  it 'extends only with base module if no nickname' do
140
140
  task = described_class.new(workflow_item)
141
141
  task.stub(:nickname).and_return(nil)
142
- task.should_receive(:extend).with(Bumbleworks::Tasks::Base)
142
+ task.should_receive(:extend).with(Bumbleworks::Task::Base)
143
143
  task.extend_module
144
144
  end
145
145
 
146
146
  it 'extends only with base module if task module does not exist' do
147
147
  task = described_class.new(workflow_item)
148
- task.should_receive(:extend).with(Bumbleworks::Tasks::Base)
148
+ task.should_receive(:extend).with(Bumbleworks::Task::Base)
149
149
  task.extend_module
150
150
  end
151
151
  end
@@ -250,6 +250,61 @@ describe Bumbleworks::Task do
250
250
  end
251
251
  end
252
252
 
253
+ describe '.for_processes' do
254
+ before :each do
255
+ Bumbleworks.define_process 'spunking' do
256
+ concurrence do
257
+ spunker :task => 'spunk'
258
+ nonspunker :task => 'complain'
259
+ end
260
+ end
261
+ Bumbleworks.define_process 'rooting' do
262
+ concurrence do
263
+ rooter :task => 'get_the_rooting_on'
264
+ armchair_critic :task => 'scoff'
265
+ end
266
+ end
267
+ @spunking_process = Bumbleworks.launch!('spunking')
268
+ @rooting_process_1 = Bumbleworks.launch!('rooting')
269
+ @rooting_process_2 = Bumbleworks.launch!('rooting')
270
+ Bumbleworks.dashboard.wait_for(:armchair_critic)
271
+ end
272
+
273
+ it 'returns tasks for given processes' do
274
+ spunking_tasks = described_class.for_processes([@spunking_process])
275
+ rooting_tasks = described_class.for_processes([@rooting_process_1])
276
+ tasks_for_both = described_class.for_processes([@spunking_process, @rooting_process_1])
277
+
278
+ spunking_tasks.map(&:nickname).should =~ ['spunk', 'complain']
279
+ rooting_tasks.map(&:nickname).should =~ ['get_the_rooting_on', 'scoff']
280
+ tasks_for_both.map(&:nickname).should =~ ['spunk', 'complain', 'get_the_rooting_on', 'scoff']
281
+ end
282
+
283
+ it 'works with process ids as well' do
284
+ spunking_tasks = described_class.for_processes([@spunking_process.id])
285
+ spunking_tasks.map(&:nickname).should =~ ['spunk', 'complain']
286
+ end
287
+
288
+ it 'returns empty array when no tasks for given process id' do
289
+ described_class.for_processes(['boop']).should be_empty
290
+ end
291
+
292
+ it 'returns empty array if given empty array' do
293
+ described_class.for_processes([]).should be_empty
294
+ end
295
+
296
+ it 'returns empty array if given nil' do
297
+ described_class.for_processes(nil).should be_empty
298
+ end
299
+ end
300
+
301
+ describe '.for_process' do
302
+ it 'acts as shortcut to .for_processes with one process' do
303
+ described_class::Finder.any_instance.should_receive(:for_processes).with([:one_guy]).and_return(:aha)
304
+ described_class.for_process(:one_guy).should == :aha
305
+ end
306
+ end
307
+
253
308
  describe '.for_role' do
254
309
  it 'returns all tasks for given role' do
255
310
  Bumbleworks.define_process 'chalking' do
@@ -315,6 +370,32 @@ describe Bumbleworks::Task do
315
370
  end
316
371
  end
317
372
 
373
+ describe '.completable' do
374
+ it 'returns only completable tasks' do
375
+ module WuggleHandsTask
376
+ def completable?
377
+ false
378
+ end
379
+ end
380
+
381
+ Bumbleworks.define_process 'hand_waggling' do
382
+ concurrence do
383
+ a_fella :task => 'waggle_hands'
384
+ a_monkey :task => 'wuggle_hands'
385
+ a_lady :task => 'wiggle_hands'
386
+ end
387
+ end
388
+ Bumbleworks.launch!('hand_waggling')
389
+ Bumbleworks.dashboard.wait_for(:a_lady)
390
+ tasks = described_class.completable
391
+ tasks.should have(2).items
392
+ tasks.map { |t| [t.role, t.nickname] }.should == [
393
+ ['a_fella', 'waggle_hands'],
394
+ ['a_lady', 'wiggle_hands']
395
+ ]
396
+ end
397
+ end
398
+
318
399
  describe '.all' do
319
400
  before :each do
320
401
  Bumbleworks.define_process 'dog-lifecycle' do
@@ -668,6 +749,12 @@ describe Bumbleworks::Task do
668
749
 
669
750
  describe 'chained queries' do
670
751
  it 'allows for AND-ed chained finders' do
752
+ module BeProudTask
753
+ def completable?
754
+ role == 'pink'
755
+ end
756
+ end
757
+
671
758
  Bumbleworks.define_process 'the_big_kachunko' do
672
759
  concurrence do
673
760
  red :task => 'be_really_mad'
@@ -690,6 +777,14 @@ describe Bumbleworks::Task do
690
777
  tasks.should have(2).items
691
778
  tasks.map(&:nickname).should =~ ['be_proud', 'be_proud']
692
779
 
780
+ tasks = described_class.
781
+ for_roles(['green', 'pink', 'blue']).
782
+ completable.
783
+ by_nickname('be_proud')
784
+ tasks.should have(1).items
785
+ tasks.map(&:nickname).should =~ ['be_proud']
786
+ tasks.first.role.should == 'pink'
787
+
693
788
  tasks = described_class.
694
789
  for_claimant('crayon_box').
695
790
  for_roles(['red', 'yellow', 'green'])