bumbleworks 0.0.4

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 (50) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +3 -0
  3. data/.ruby-version +1 -0
  4. data/.watchr +89 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +84 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +160 -0
  9. data/Rakefile +9 -0
  10. data/bumbleworks.gemspec +30 -0
  11. data/doc/GUIDE.md +337 -0
  12. data/doc/TERMS.md +9 -0
  13. data/lib/bumbleworks.rb +123 -0
  14. data/lib/bumbleworks/configuration.rb +182 -0
  15. data/lib/bumbleworks/hash_storage.rb +13 -0
  16. data/lib/bumbleworks/participant_registration.rb +19 -0
  17. data/lib/bumbleworks/process_definition.rb +143 -0
  18. data/lib/bumbleworks/ruote.rb +64 -0
  19. data/lib/bumbleworks/storage_adapter.rb +23 -0
  20. data/lib/bumbleworks/support.rb +20 -0
  21. data/lib/bumbleworks/task.rb +109 -0
  22. data/lib/bumbleworks/tree_builder.rb +60 -0
  23. data/lib/bumbleworks/version.rb +3 -0
  24. data/spec/fixtures/apps/with_default_directories/app/participants/honey_participant.rb +3 -0
  25. data/spec/fixtures/apps/with_default_directories/app/participants/molasses_participant.rb +3 -0
  26. data/spec/fixtures/apps/with_default_directories/config_initializer.rb +4 -0
  27. data/spec/fixtures/apps/with_default_directories/full_initializer.rb +12 -0
  28. data/spec/fixtures/apps/with_default_directories/lib/process_definitions/garbage_collector.rb +3 -0
  29. data/spec/fixtures/apps/with_default_directories/lib/process_definitions/make_honey.rb +3 -0
  30. data/spec/fixtures/apps/with_default_directories/lib/process_definitions/make_molasses.rb +6 -0
  31. data/spec/fixtures/apps/with_specified_directories/config_initializer.rb +5 -0
  32. data/spec/fixtures/apps/with_specified_directories/specific_directory/definitions/.gitkeep +0 -0
  33. data/spec/fixtures/apps/with_specified_directories/specific_directory/participants/.gitkeep +0 -0
  34. data/spec/fixtures/definitions/a_list_of_jams.rb +4 -0
  35. data/spec/fixtures/definitions/nested_folder/test_nested_process.rb +3 -0
  36. data/spec/fixtures/definitions/test_process.rb +5 -0
  37. data/spec/integration/configuration_spec.rb +43 -0
  38. data/spec/integration/sample_application_spec.rb +45 -0
  39. data/spec/lib/bumbleworks/configuration_spec.rb +162 -0
  40. data/spec/lib/bumbleworks/participant_registration_spec.rb +13 -0
  41. data/spec/lib/bumbleworks/process_definition_spec.rb +178 -0
  42. data/spec/lib/bumbleworks/ruote_spec.rb +107 -0
  43. data/spec/lib/bumbleworks/storage_adapter_spec.rb +41 -0
  44. data/spec/lib/bumbleworks/support_spec.rb +40 -0
  45. data/spec/lib/bumbleworks/task_spec.rb +274 -0
  46. data/spec/lib/bumbleworks/tree_builder_spec.rb +95 -0
  47. data/spec/lib/bumbleworks_spec.rb +133 -0
  48. data/spec/spec_helper.rb +20 -0
  49. data/spec/support/path_helpers.rb +11 -0
  50. metadata +262 -0
@@ -0,0 +1,41 @@
1
+ describe Bumbleworks::StorageAdapter do
2
+ describe '.auto_register?' do
3
+ it 'returns true if auto_register is true' do
4
+ described_class.auto_register = true
5
+ described_class.auto_register?.should be_true
6
+ end
7
+
8
+ it 'returns false if auto_register is not true' do
9
+ described_class.auto_register = :ghosts
10
+ described_class.auto_register?.should be_false
11
+ end
12
+
13
+ it 'is true by default' do
14
+ described_class.auto_register = nil
15
+ described_class.auto_register?.should be_true
16
+ end
17
+ end
18
+
19
+ describe '.display_name' do
20
+ it 'is a subclass responsibility' do
21
+ expect { described_class.display_name }.to raise_error
22
+ end
23
+ end
24
+
25
+ describe '.driver' do
26
+ it 'is a subclass responsibility' do
27
+ expect { described_class.driver }.to raise_error
28
+ end
29
+ end
30
+
31
+ describe '.use?' do
32
+ before :each do
33
+ described_class.stub(:display_name).and_return('String')
34
+ end
35
+
36
+ it 'returns true if argument class name matches display name' do
37
+ described_class.use?('a string').should be_true
38
+ described_class.use?(:not_a_string).should be_false
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ describe Bumbleworks::Support do
2
+ describe '.camelize' do
3
+ it 'turns underscored string into camelcase' do
4
+ described_class.camelize('foo_bar_One_two_3').should == 'FooBarOneTwo3'
5
+ end
6
+
7
+ it 'deals with nested classes' do
8
+ described_class.camelize('foo_bar/bar_foo').should == 'FooBar::BarFoo'
9
+ end
10
+ end
11
+
12
+ describe ".all_files" do
13
+ let(:test_directory) { File.join(fixtures_path, 'definitions').to_s }
14
+
15
+ it "for given directory, yields given block with path and name params" do
16
+ assembled_hash = {}
17
+ described_class.all_files(test_directory) do |path, name|
18
+ assembled_hash[name] = path
19
+ end
20
+
21
+ assembled_hash['test_process'].should ==
22
+ File.join(fixtures_path, 'definitions', 'test_process.rb').to_s
23
+ assembled_hash['test_nested_process'].should ==
24
+ File.join(fixtures_path, 'definitions', 'nested_folder', 'test_nested_process.rb').to_s
25
+ end
26
+
27
+ it "camelizes names if :camelize option is true " do
28
+ path = File.join(fixtures_path, 'definitions')
29
+ assembled_hash = {}
30
+ described_class.all_files(test_directory, :camelize => true) do |path, name|
31
+ assembled_hash[name] = path
32
+ end
33
+
34
+ assembled_hash['TestProcess'].should ==
35
+ File.join(fixtures_path, 'definitions', 'test_process.rb').to_s
36
+ assembled_hash['TestNestedProcess'].should ==
37
+ File.join(fixtures_path, 'definitions', 'nested_folder', 'test_nested_process.rb').to_s
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,274 @@
1
+ describe Bumbleworks::Task do
2
+ let(:workflow_item) {Ruote::Workitem.new('fields' => {'params' => {'task' => 'go_to_work'} })}
3
+
4
+ before :each do
5
+ Bumbleworks.reset!
6
+ Bumbleworks.autostart_worker = true
7
+ Bumbleworks.storage = {}
8
+ Bumbleworks::Ruote.register_participants
9
+ end
10
+
11
+ describe '.new' do
12
+ it 'raises an error if workitem is nil' do
13
+ expect {
14
+ described_class.new(nil)
15
+ }.to raise_error(ArgumentError, "Not a valid workitem")
16
+ end
17
+
18
+ it 'raises an error if workitem not a Ruote::Workitem' do
19
+ expect {
20
+ described_class.new('a string!')
21
+ }.to raise_error(ArgumentError, "Not a valid workitem")
22
+ end
23
+
24
+ it 'succeeds when given workitem' do
25
+ expect {
26
+ described_class.new(workflow_item)
27
+ }.not_to raise_error
28
+ end
29
+ end
30
+
31
+ describe '#id' do
32
+ it 'returns the sid from the workitem' do
33
+ workflow_item.stub(:sid).and_return(:an_exciting_id)
34
+ described_class.new(workflow_item).id.should == :an_exciting_id
35
+ end
36
+ end
37
+
38
+ describe '.find_by_id' do
39
+ it 'returns the task for the given id' do
40
+ Bumbleworks.define_process 'planting_a_noodle' do
41
+ concurrence do
42
+ noodle_gardener :task => 'plant_noodle_seed'
43
+ horse_feeder :task => 'give_the_horse_a_bon_bon'
44
+ end
45
+ end
46
+ Bumbleworks.launch!('planting_a_noodle')
47
+ Bumbleworks.dashboard.wait_for(:horse_feeder)
48
+ plant_noodle_seed_task = described_class.for_role('noodle_gardener').first
49
+ give_the_horse_a_bon_bon_task = described_class.for_role('horse_feeder').first
50
+
51
+ # checking for equality by comparing sid, which is the flow expression id
52
+ # that identifies not only the expression, but its instance
53
+ described_class.find_by_id(plant_noodle_seed_task.id).sid.should ==
54
+ plant_noodle_seed_task.sid
55
+ described_class.find_by_id(give_the_horse_a_bon_bon_task.id).sid.should ==
56
+ give_the_horse_a_bon_bon_task.sid
57
+ end
58
+
59
+ it 'raises an error if id is nil' do
60
+ expect {
61
+ described_class.find_by_id(nil)
62
+ }.to raise_error(described_class::MissingWorkitem)
63
+ end
64
+
65
+ it 'raises an error if workitem not found for given id' do
66
+ expect {
67
+ described_class.find_by_id('asdfasdf')
68
+ }.to raise_error(described_class::MissingWorkitem)
69
+ end
70
+
71
+ it 'raises an error if id is unparseable by storage participant' do
72
+ expect {
73
+ described_class.find_by_id(:unparseable_because_i_am_a_symbol)
74
+ }.to raise_error(described_class::MissingWorkitem)
75
+ end
76
+ end
77
+
78
+ describe '.for_roles' do
79
+ before :each do
80
+ Bumbleworks.define_process 'lowering_penguin_self_esteem' do
81
+ concurrence do
82
+ heckler :task => 'comment_on_dancing_ability'
83
+ mother :oh_no => 'this_is_not_a_task'
84
+ mother :task => 'ignore_pleas_for_attention'
85
+ father :task => 'sit_around_watching_penguin_tv'
86
+ end
87
+ end
88
+ Bumbleworks.launch!('lowering_penguin_self_esteem')
89
+ end
90
+
91
+ it 'returns tasks for all given roles' do
92
+ Bumbleworks.dashboard.wait_for(:father)
93
+ tasks = described_class.for_roles(['heckler', 'mother'])
94
+ tasks.should have(2).items
95
+ tasks.map(&:nickname).should == [
96
+ 'comment_on_dancing_ability',
97
+ 'ignore_pleas_for_attention'
98
+ ]
99
+ end
100
+
101
+ it 'returns empty array if no tasks found for given roles' do
102
+ Bumbleworks.dashboard.wait_for(:father)
103
+ described_class.for_roles(['elephant']).should be_empty
104
+ end
105
+
106
+ it 'returns empty array if given empty array' do
107
+ Bumbleworks.dashboard.wait_for(:father)
108
+ described_class.for_roles([]).should be_empty
109
+ end
110
+
111
+ it 'returns empty array if given nil' do
112
+ Bumbleworks.dashboard.wait_for(:father)
113
+ described_class.for_roles(nil).should be_empty
114
+ end
115
+ end
116
+
117
+ describe '.for_role' do
118
+ it 'delegates to #for_roles with single-item array' do
119
+ described_class.should_receive(:for_roles).with(['mister_mystery'])
120
+ described_class.for_role('mister_mystery')
121
+ end
122
+ end
123
+
124
+ describe '.all' do
125
+ before :each do
126
+ Bumbleworks.define_process 'dog-lifecycle' do
127
+ concurrence do
128
+ dog_teeth :task => 'eat'
129
+ dog_mouth :task => 'bark'
130
+ everyone :task => 'pet_dog'
131
+ the_universe_is_wonderful
132
+ dog_legs :task => 'skip_and_jump'
133
+ end
134
+ dog_brain :task => 'nap'
135
+ end
136
+ Bumbleworks.launch!('dog-lifecycle')
137
+ end
138
+
139
+ it 'returns all tasks (with task param) in queue regardless of role' do
140
+ Bumbleworks.dashboard.wait_for(:dog_legs)
141
+ tasks = described_class.all
142
+ tasks.should have(4).items
143
+ tasks.map { |t| [t.role, t.nickname] }.should == [
144
+ ['dog_teeth', 'eat'],
145
+ ['dog_mouth', 'bark'],
146
+ ['everyone', 'pet_dog'],
147
+ ['dog_legs', 'skip_and_jump']
148
+ ]
149
+ end
150
+ end
151
+
152
+ describe '#[], #[]=' do
153
+ subject{described_class.new(workflow_item)}
154
+ it 'sets values on workitem fields' do
155
+ subject['hive'] = 'bees at work'
156
+ workflow_item.fields['hive'].should == 'bees at work'
157
+ end
158
+
159
+ it 'retuns value from workitem params' do
160
+ workflow_item.fields['nest'] = 'queen resting'
161
+ subject['nest'].should == 'queen resting'
162
+ end
163
+ end
164
+
165
+ describe '#nickname' do
166
+ it 'returns the "task" param' do
167
+ described_class.new(workflow_item).nickname.should == 'go_to_work'
168
+ end
169
+
170
+ it 'is immutable; cannot be changed by modified the param' do
171
+ task = described_class.new(workflow_item)
172
+ task.nickname.should == 'go_to_work'
173
+ task.params['task'] = 'what_is_wrong_with_you?'
174
+ task.nickname.should == 'go_to_work'
175
+ end
176
+ end
177
+
178
+ describe '#role' do
179
+ it 'returns the workitem participant_name' do
180
+ Bumbleworks.define_process 'planting_a_noodle' do
181
+ noodle_gardener :task => 'plant_noodle_seed'
182
+ end
183
+ Bumbleworks.launch!('planting_a_noodle')
184
+ Bumbleworks.dashboard.wait_for(:noodle_gardener)
185
+ described_class.all.first.role.should == 'noodle_gardener'
186
+ end
187
+ end
188
+
189
+ context 'claiming things' do
190
+ subject{described_class.new(workflow_item)}
191
+ before :each do
192
+ subject.stub(:save)
193
+ workflow_item.params['claimant'] = nil
194
+ subject.claim('boss')
195
+ end
196
+
197
+ describe '#claim' do
198
+ it 'sets token on "claimant" param' do
199
+ workflow_item.params['claimant'].should == 'boss'
200
+ end
201
+
202
+ it 'raises an error if already claimed by someone else' do
203
+ expect{subject.claim('peon')}.to raise_error described_class::AlreadyClaimed
204
+ end
205
+
206
+ it 'does not raise an error if attempting to claim by same token' do
207
+ expect{subject.claim('boss')}.not_to raise_error described_class::AlreadyClaimed
208
+ end
209
+ end
210
+
211
+ describe '#claimant' do
212
+ it 'returns token of who has claim' do
213
+ subject.claimant.should == 'boss'
214
+ end
215
+ end
216
+
217
+ describe '#claimed?' do
218
+ it 'returns true if claimed' do
219
+ subject.claimed?.should be_true
220
+ end
221
+
222
+ it 'false otherwise' do
223
+ workflow_item.params['claimant'] = nil
224
+ subject.claimed?.should be_false
225
+ end
226
+ end
227
+
228
+ describe '#release' do
229
+ it "release claim on workitem" do
230
+ subject.should be_claimed
231
+ subject.release
232
+ subject.should_not be_claimed
233
+ end
234
+ end
235
+ end
236
+
237
+ context 'updating workflow engine' do
238
+ before :each do
239
+ Bumbleworks.define_process 'dog-lifecycle' do
240
+ dog_mouth :task => 'eat_dinner', :state => 'still cooking'
241
+ dog_brain :task => 'cat_nap', :by => 'midnight'
242
+ end
243
+ Bumbleworks.launch!('dog-lifecycle')
244
+ end
245
+
246
+ describe '#save' do
247
+ it 'saves fields and params, but does not proceed process' do
248
+ event = Bumbleworks.dashboard.wait_for :dog_mouth
249
+ task = described_class.for_role('dog_mouth').first
250
+ task.params['state'] = 'is ready'
251
+ task.fields['meal'] = 'salted_rhubarb'
252
+ task.save
253
+ task = described_class.for_role('dog_mouth').first
254
+ task.params['state'].should == 'is ready'
255
+ task.fields['meal'].should == 'salted_rhubarb'
256
+ end
257
+ end
258
+
259
+ describe '#complete' do
260
+ it 'saves fields and proceeds to next expression' do
261
+ event = Bumbleworks.dashboard.wait_for :dog_mouth
262
+ task = described_class.for_role('dog_mouth').first
263
+ task.params['state'] = 'is ready'
264
+ task.fields['meal'] = 'root beer and a kite'
265
+ task.complete
266
+ described_class.for_role('dog_mouth').should be_empty
267
+ event = Bumbleworks.dashboard.wait_for :dog_brain
268
+ task = described_class.for_role('dog_brain').first
269
+ task.params['state'].should be_nil
270
+ task.fields['meal'].should == 'root beer and a kite'
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,95 @@
1
+ describe Bumbleworks::TreeBuilder do
2
+ describe '.new' do
3
+ it 'raises error if no definition or tree' do
4
+ expect { described_class.new }.to raise_error(ArgumentError)
5
+ end
6
+
7
+ it 'raises error if both definition and tree' do
8
+ expect {
9
+ described_class.new(:definition => :foo, :tree => :bar)
10
+ }.to raise_error(ArgumentError)
11
+ end
12
+
13
+ it 'succeeds if only one of definition or tree specified' do
14
+ expect { described_class.new(:definition => :foo) }.not_to raise_error
15
+ expect { described_class.new(:tree => :foo) }.not_to raise_error
16
+ end
17
+ end
18
+
19
+ describe '#build!' do
20
+ it 'builds tree and sets name to given name' do
21
+ pdef = %q(
22
+ Bumbleworks.define_process do
23
+ order_pants_around
24
+ end
25
+ )
26
+ builder = described_class.new(:name => 'luigi', :definition => pdef)
27
+ builder.build!.should == [
28
+ "define", {"name" => "luigi"},
29
+ [
30
+ ["order_pants_around", {}, []]
31
+ ]
32
+ ]
33
+ end
34
+
35
+ it 'uses tree if given' do
36
+ tree = ["define", {"guppies" => nil}, [["swim", {}, []]]]
37
+ builder = described_class.new(:tree => tree)
38
+ builder.build!.should ==
39
+ ["define", {"name" => "guppies"}, [["swim", {}, []]]]
40
+ end
41
+
42
+ it 'normalizes and sets name from tree' do
43
+ pdef = %q(
44
+ Bumbleworks.define_process 'country_time_county_dime' do
45
+ do_the_fig_newton
46
+ end
47
+ )
48
+ builder = described_class.new(:definition => pdef)
49
+ builder.build!.should == [
50
+ "define", {"name" => "country_time_county_dime"},
51
+ [
52
+ ["do_the_fig_newton", {}, []]
53
+ ]
54
+ ]
55
+ end
56
+
57
+ it 'raises error if name conflicts with name in definition' do
58
+ pdef = %q(
59
+ Bumbleworks.define_process :name => 'stromboli' do
60
+ order_pants_around
61
+ end
62
+ )
63
+ builder = described_class.new(:name => 'contorti', :definition => pdef)
64
+ expect {
65
+ builder.build!
66
+ }.to raise_error(described_class::InvalidTree, "Name does not match name in definition" )
67
+ end
68
+
69
+ it 'raises error if process is unparseable' do
70
+ pdef = "A quiet evening with the deaf lemurs"
71
+ builder = described_class.new(:name => 'lemur_joy', :definition => pdef)
72
+ expect {
73
+ builder.build!
74
+ }.to raise_error(described_class::InvalidTree, "cannot read process definition" )
75
+ end
76
+ end
77
+
78
+ describe '.from_definition' do
79
+ it 'instantiates a TreeBuilder from a given block' do
80
+ builder = described_class.from_definition 'just_another_poodle_day' do
81
+ chew :on => 'dad'
82
+ construct :solution => 'to world conflict'
83
+ end
84
+ builder.build!
85
+ builder.tree.should ==
86
+ ["define", { "name" => "just_another_poodle_day" },
87
+ [
88
+ ["chew", {"on" => "dad"}, []],
89
+ ["construct", {"solution" => "to world conflict"}, []]
90
+ ]
91
+ ]
92
+ builder.name.should == 'just_another_poodle_day'
93
+ end
94
+ end
95
+ end