bumbleworks 0.0.48 → 0.0.50

Sign up to get free protection for your applications and to get access to all the features.
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'])