bumbleworks-api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +25 -0
  8. data/Rakefile +9 -0
  9. data/bumbleworks-api.gemspec +32 -0
  10. data/config.ru +13 -0
  11. data/lib/bumbleworks/api.rb +15 -0
  12. data/lib/bumbleworks/api/config/application.rb +10 -0
  13. data/lib/bumbleworks/api/config/routes.rb +34 -0
  14. data/lib/bumbleworks/api/controllers/application_controller.rb +6 -0
  15. data/lib/bumbleworks/api/controllers/entities_controller.rb +25 -0
  16. data/lib/bumbleworks/api/controllers/errors_controller.rb +32 -0
  17. data/lib/bumbleworks/api/controllers/expressions_controller.rb +29 -0
  18. data/lib/bumbleworks/api/controllers/processes_controller.rb +29 -0
  19. data/lib/bumbleworks/api/controllers/tasks_controller.rb +43 -0
  20. data/lib/bumbleworks/api/controllers/trackers_controller.rb +13 -0
  21. data/lib/bumbleworks/api/controllers/workers_controller.rb +51 -0
  22. data/lib/bumbleworks/api/lib/presenter.rb +32 -0
  23. data/lib/bumbleworks/api/lib/presenters/entity_class_presenter.rb +19 -0
  24. data/lib/bumbleworks/api/lib/presenters/entity_presenter.rb +13 -0
  25. data/lib/bumbleworks/api/lib/presenters/error_presenter.rb +15 -0
  26. data/lib/bumbleworks/api/lib/presenters/expression_presenter.rb +17 -0
  27. data/lib/bumbleworks/api/lib/presenters/process_presenter.rb +32 -0
  28. data/lib/bumbleworks/api/lib/presenters/task_presenter.rb +15 -0
  29. data/lib/bumbleworks/api/lib/presenters/tracker_presenter.rb +16 -0
  30. data/lib/bumbleworks/api/lib/presenters/worker_presenter.rb +30 -0
  31. data/lib/bumbleworks/api/lib/time_support.rb +23 -0
  32. data/lib/bumbleworks/api/version.rb +5 -0
  33. data/playground_setup.rb +30 -0
  34. data/spec/controllers/entities_controller_spec.rb +32 -0
  35. data/spec/controllers/errors_controller_spec.rb +42 -0
  36. data/spec/controllers/expressions_controller_spec.rb +40 -0
  37. data/spec/controllers/processes_controller_spec.rb +50 -0
  38. data/spec/controllers/tasks_controller_spec.rb +82 -0
  39. data/spec/controllers/trackers_controller_spec.rb +25 -0
  40. data/spec/controllers/workers_controller_spec.rb +113 -0
  41. data/spec/fixtures/bumbleworks_config.rb +10 -0
  42. data/spec/fixtures/entities/mock_entity.rb +38 -0
  43. data/spec/fixtures/entities/widget.rb +9 -0
  44. data/spec/fixtures/entities/widgety_fidget.rb +5 -0
  45. data/spec/fixtures/participants.rb +3 -0
  46. data/spec/fixtures/participants/naughty_participant.rb +16 -0
  47. data/spec/fixtures/processes/error_process.rb +3 -0
  48. data/spec/fixtures/processes/task_process.rb +9 -0
  49. data/spec/fixtures/processes/waiting_process.rb +8 -0
  50. data/spec/lib/presenter_spec.rb +29 -0
  51. data/spec/lib/presenters/entity_class_presenter_spec.rb +17 -0
  52. data/spec/lib/presenters/entity_presenter_spec.rb +15 -0
  53. data/spec/lib/presenters/error_presenter_spec.rb +18 -0
  54. data/spec/lib/presenters/expression_presenter_spec.rb +33 -0
  55. data/spec/lib/presenters/process_presenter_spec.rb +42 -0
  56. data/spec/lib/presenters/task_presenter_spec.rb +18 -0
  57. data/spec/lib/presenters/tracker_presenter_spec.rb +18 -0
  58. data/spec/lib/presenters/worker_presenter_spec.rb +30 -0
  59. data/spec/lib/time_support_spec.rb +42 -0
  60. data/spec/spec_helper.rb +39 -0
  61. data/spec/support/api_helper.rb +23 -0
  62. data/spec/support/process_helpers.rb +11 -0
  63. metadata +261 -0
@@ -0,0 +1,25 @@
1
+ describe Bumbleworks::Api::TrackersController do
2
+ before(:each) do
3
+ Bumbleworks.launch!('waiting_process')
4
+ wait_until { Bumbleworks::Tracker.count == 5 }
5
+ end
6
+
7
+ describe "#index" do
8
+ it "returns all existing trackers" do
9
+ get "/trackers"
10
+ expect(last_response.body).to eq(
11
+ json_presentation_of(Bumbleworks::Tracker.all, :as => 'Tracker')
12
+ )
13
+ end
14
+ end
15
+
16
+ describe "#show" do
17
+ it "returns requested instance" do
18
+ tracker = Bumbleworks::Tracker.all.first
19
+ get "/trackers/#{tracker.id}"
20
+ expect(last_response.body).to eq(
21
+ json_presentation_of(tracker, :as => 'Tracker')
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,113 @@
1
+ describe Bumbleworks::Api::WorkersController do
2
+ before(:each) do
3
+ Bumbleworks.start_worker!
4
+ allow(Time).to receive(:now).and_return(Time.now)
5
+ end
6
+
7
+ describe "#index" do
8
+ it "returns all workers" do
9
+ allow(Time).to receive(:now).and_return(Time.now)
10
+ workers = Bumbleworks::Worker::Info.all
11
+ expect(Bumbleworks::Worker).to receive(:refresh_worker_info)
12
+ get "/workers"
13
+ expect(last_response.body).to eq(
14
+ json_presentation_of(workers, :as => 'Worker')
15
+ )
16
+ end
17
+ end
18
+
19
+ describe "#show" do
20
+ it "returns requested worker" do
21
+ worker = Bumbleworks::Worker::Info.first
22
+ expect(Bumbleworks::Worker).to receive(:refresh_worker_info)
23
+ get "/workers/#{worker.id}"
24
+ expect(last_response.body).to eq(
25
+ json_presentation_of(worker, :as => 'Worker')
26
+ )
27
+ end
28
+ end
29
+
30
+ describe "#change_state" do
31
+ let(:worker) { Bumbleworks::Worker::Info.first }
32
+ before(:each) do
33
+ allow(Bumbleworks::Worker::Info).to receive(:[]).
34
+ with(worker.id).and_return(worker)
35
+ end
36
+
37
+ it "sends shutdown to given worker" do
38
+ expect(worker).to receive(:shutdown)
39
+ put "/workers/#{worker.id}/command/shutdown"
40
+ expect(last_response.body).to eq(
41
+ { "status" => "shutdown sent" }.to_json
42
+ )
43
+ end
44
+
45
+ it "sends pause to given worker" do
46
+ expect(worker).to receive(:pause)
47
+ put "/workers/#{worker.id}/command/pause"
48
+ expect(last_response.body).to eq(
49
+ { "status" => "pause sent" }.to_json
50
+ )
51
+ end
52
+
53
+ it "sends unpause to given worker" do
54
+ expect(worker).to receive(:unpause)
55
+ put "/workers/#{worker.id}/command/unpause"
56
+ expect(last_response.body).to eq(
57
+ { "status" => "unpause sent" }.to_json
58
+ )
59
+ end
60
+
61
+ it "returns 404 if unknown command is sent" do
62
+ put "/workers/#{worker.id}/command/dance"
63
+ expect(last_response.body).to eq(
64
+ { :error => "not_found" }.to_json
65
+ )
66
+ expect(last_response.status).to eq(404)
67
+ end
68
+ end
69
+
70
+ describe "#change_global_state" do
71
+ it "sends shutdown to all workers" do
72
+ expect(Bumbleworks::Worker).to receive(:shutdown_all)
73
+ put "/workers/command/shutdown"
74
+ expect(last_response.body).to eq(
75
+ { "status" => "shutdown sent" }.to_json
76
+ )
77
+ end
78
+
79
+ it "sends pause to all workers" do
80
+ expect(Bumbleworks::Worker).to receive(:pause_all)
81
+ put "/workers/command/pause"
82
+ expect(last_response.body).to eq(
83
+ { "status" => "pause sent" }.to_json
84
+ )
85
+ end
86
+
87
+ it "sends unpause to all workers" do
88
+ expect(Bumbleworks::Worker).to receive(:unpause_all)
89
+ put "/workers/command/unpause"
90
+ expect(last_response.body).to eq(
91
+ { "status" => "unpause sent" }.to_json
92
+ )
93
+ end
94
+
95
+ it "returns 404 if unknown command is sent" do
96
+ put "/workers/command/dance"
97
+ expect(last_response.body).to eq(
98
+ { :error => "not_found" }.to_json
99
+ )
100
+ expect(last_response.status).to eq(404)
101
+ end
102
+ end
103
+
104
+ describe "#prune_stopped_workers" do
105
+ it "prunes stale worker info" do
106
+ expect(Bumbleworks::Worker::Info).to receive(:purge_stale_worker_info)
107
+ delete "/workers/prune"
108
+ expect(last_response.body).to eq(
109
+ { :status => "pruned_stale_worker_info" }.to_json
110
+ )
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'entities/widget'
2
+ require_relative 'entities/widgety_fidget'
3
+
4
+ Bumbleworks.configure do |c|
5
+ c.storage = {}
6
+ c.root = File.dirname(__FILE__)
7
+ end
8
+
9
+ Bumbleworks.bootstrap!
10
+ Bumbleworks.initialize!
@@ -0,0 +1,38 @@
1
+ class MockEntity
2
+ class << self
3
+ def collection
4
+ @collection ||= {}
5
+ end
6
+
7
+ def all
8
+ collection.values
9
+ end
10
+
11
+ def truncate!
12
+ @collection = {}
13
+ end
14
+
15
+ def first_by_identifier(identifier)
16
+ collection[identifier.to_i]
17
+ end
18
+
19
+ def count
20
+ all.count
21
+ end
22
+ end
23
+
24
+ def initialize(identifier)
25
+ @identifier = identifier
26
+ self.class.collection[identifier] = self
27
+ end
28
+
29
+ def id
30
+ @identifier
31
+ end
32
+
33
+ def update(hsh)
34
+ hsh.each do |key, value|
35
+ self.send(:"#{key}=", value)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'mock_entity'
2
+
3
+ class Widget < MockEntity
4
+ include Bumbleworks::Entity
5
+
6
+ process :task_process
7
+
8
+ attr_accessor :task_process_identifier
9
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'mock_entity'
2
+
3
+ class WidgetyFidget < MockEntity
4
+ include Bumbleworks::Entity
5
+ end
@@ -0,0 +1,3 @@
1
+ Bumbleworks.register_participants do
2
+ fall_apart NaughtyParticipant
3
+ end
@@ -0,0 +1,16 @@
1
+ class NaughtyParticipant
2
+ class StupidError < StandardError; end
3
+ include Bumbleworks::LocalParticipant
4
+
5
+ class << self
6
+ attr_accessor :naughty_is_ok
7
+ end
8
+
9
+ def on_workitem
10
+ if self.class.naughty_is_ok
11
+ reply
12
+ else
13
+ raise StupidError, 'Oh crumb.'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ Bumbleworks.define_process do
2
+ fall_apart
3
+ end
@@ -0,0 +1,9 @@
1
+ Bumbleworks.define_process do
2
+ concurrence do
3
+ admin :task => 'do_something'
4
+ peon :task => 'do_something_dumb'
5
+ end
6
+ fall_apart
7
+ peon :task => 'meekly_do_another_thing'
8
+ admin :task => 'confront'
9
+ end
@@ -0,0 +1,8 @@
1
+ Bumbleworks.define_process do
2
+ concurrence do
3
+ wait_for_event :the_event, :where => :entities_match
4
+ await :left_tag => 'local_event'
5
+ await :error => /bad/
6
+ await :participant => %w[goose bunnies]
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ describe Bumbleworks::Api::Presenter do
2
+ describe ".from_array" do
3
+ it "maps each object to its presentation, passing in_collection arg" do
4
+ allow(described_class).to receive(:new).
5
+ with(:a, :in_collection => true).
6
+ and_return(:a_p)
7
+ allow(described_class).to receive(:new).
8
+ with(:b, :in_collection => true).
9
+ and_return(:b_p)
10
+ expect(described_class.from_array([:a, :b])).to eq(
11
+ [:a_p, :b_p]
12
+ )
13
+ end
14
+ end
15
+
16
+ describe ".present" do
17
+ it "instantiates single presenter if not given array" do
18
+ allow(described_class).to receive(:new).with(:a).and_return(:a_p)
19
+ expect(described_class.present(:a)).to eq(:a_p)
20
+ end
21
+
22
+ it "returns result of .from_array if given array" do
23
+ allow(described_class).to receive(:from_array).
24
+ with([:a, :b]).
25
+ and_return(:presented_array)
26
+ expect(described_class.present([:a, :b])).to eq(:presented_array)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ describe Bumbleworks::Api::EntityClassPresenter do
2
+ let(:presented) { Widget }
3
+ subject { described_class.new(presented) }
4
+
5
+ describe "#to_hash" do
6
+ it "returns hash representation of entity class" do
7
+ allow(presented).to receive(:count).and_return(10)
8
+ expect(subject.to_hash).to eq({
9
+ :class => 'Widget',
10
+ :count => 10,
11
+ :registered_processes => [
12
+ { :name => :task_process, :attribute => :task_process_identifier }
13
+ ]
14
+ })
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ describe Bumbleworks::Api::EntityPresenter do
2
+ let(:presented) { Widget.new(42) }
3
+ subject { described_class.new(presented) }
4
+
5
+ describe "#to_hash" do
6
+ it "returns hash representation of entity class" do
7
+ allow(presented).to receive(:processes).and_return([:a, :b])
8
+ expect(subject.to_hash).to eq({
9
+ :identifier => 42,
10
+ :name => "Widget 42",
11
+ :process_count => 2
12
+ })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ describe Bumbleworks::Api::ErrorPresenter do
2
+ let(:presented) { Bumbleworks.errors.first }
3
+ subject { described_class.new(presented) }
4
+
5
+ describe "#to_hash" do
6
+ it "returns hash representation of error" do
7
+ process = Bumbleworks.launch!('error_process')
8
+ wait_until { Bumbleworks.errors.count == 1 }
9
+ expect(subject.to_hash).to eq({
10
+ :process_id => process.id,
11
+ :expression_id => presented.expression.expid,
12
+ :error_class_name => "NaughtyParticipant::StupidError",
13
+ :message => "Oh crumb.",
14
+ :backtrace => presented.backtrace
15
+ })
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ describe Bumbleworks::Api::ExpressionPresenter do
2
+ let(:process) { Bumbleworks.launch!('task_process') }
3
+ let(:presented) { process.expression_at_position('0_0_1') }
4
+
5
+ subject { described_class.new(presented) }
6
+
7
+ describe "#to_hash" do
8
+ it "returns hash representation of expression" do
9
+ wait_until { process.tasks.count == 2 }
10
+ expect(subject.to_hash).to eq({
11
+ :expression_id => presented.expid,
12
+ :process_id => presented.process.id,
13
+ :tree => presented.tree
14
+ })
15
+ end
16
+
17
+ context "with error" do
18
+ let(:process) { Bumbleworks.launch!('error_process') }
19
+ let(:presented) { process.expression_at_position('0_0') }
20
+
21
+ it "includes presented error" do
22
+ wait_until { process.reload.errors.count == 1 }
23
+ error = Bumbleworks.errors.first
24
+ expect(subject.to_hash).to eq({
25
+ :expression_id => presented.expid,
26
+ :process_id => presented.process.id,
27
+ :tree => presented.tree,
28
+ :error => Bumbleworks::Api::ErrorPresenter.present(error).to_hash
29
+ })
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ describe Bumbleworks::Api::ProcessPresenter do
2
+ let(:presented) { Bumbleworks.launch!('task_process', :entity => widget) }
3
+ let(:widget) { Widget.new(41) }
4
+
5
+ describe "#to_hash" do
6
+ context "not in collection" do
7
+ subject { described_class.new(presented) }
8
+
9
+ it "returns full hash representation of process" do
10
+ wait_until { presented.tasks.count == 2 }
11
+ expect(subject.to_hash).to eq({
12
+ :id => presented.id,
13
+ :definition_name => "task_process",
14
+ :subscribed_events => [],
15
+ :entity => Bumbleworks::Api::EntityPresenter.present(widget).to_hash,
16
+ :entity_name => widget.to_s,
17
+ :original_tree => presented.original_tree
18
+ })
19
+ end
20
+
21
+ it "doesn't include entity if process has none" do
22
+ wait_until { presented.tasks.count == 2 }
23
+ allow(presented).to receive(:entity).and_return(nil)
24
+ expect(subject.to_hash.keys).not_to include(:entity)
25
+ end
26
+ end
27
+
28
+ context "in collection" do
29
+ subject { described_class.new(presented, :in_collection => true) }
30
+
31
+ it "returns minimal hash representation of process" do
32
+ wait_until { presented.tasks.count == 2 }
33
+ expect(subject.to_hash).to eq({
34
+ :id => presented.id,
35
+ :definition_name => "task_process",
36
+ :entity_name => widget.to_s,
37
+ :subscribed_events => []
38
+ })
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ describe Bumbleworks::Api::TaskPresenter do
2
+ let(:process) { Bumbleworks.launch!('task_process') }
3
+ let(:presented) { process.tasks.next_available }
4
+ subject { described_class.new(presented) }
5
+
6
+ describe "#to_hash" do
7
+ it "returns hash representation of entity class" do
8
+ presented.claim('oscar')
9
+ expect(subject.to_hash).to eq({
10
+ :id => presented.id,
11
+ :name => presented.to_s,
12
+ :claimant => 'oscar',
13
+ :process_id => presented.wfid,
14
+ :role => presented.role
15
+ })
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ describe Bumbleworks::Api::TrackerPresenter do
2
+ let(:process) { Bumbleworks.launch!('waiting_process') }
3
+ let(:presented) { Bumbleworks::Tracker.all.first }
4
+ subject { described_class.new(presented) }
5
+
6
+ describe "#to_hash" do
7
+ it "returns hash representation of entity class" do
8
+ expect(subject.to_hash).to eq({
9
+ :id => presented.id,
10
+ :waiting_expression => presented.waiting_expression,
11
+ :original_hash => presented.original_hash,
12
+ :action => presented.action,
13
+ :conditions => presented.conditions,
14
+ :process_id => presented.wfid
15
+ })
16
+ end
17
+ end
18
+ end