bumbleworks-api 0.0.1

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. 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,32 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class Presenter
4
+ attr_reader :presented
5
+
6
+ class << self
7
+ def present(presented)
8
+ if presented.is_a?(Array)
9
+ from_array(presented)
10
+ else
11
+ new(presented)
12
+ end
13
+ end
14
+
15
+ def from_array(array)
16
+ array.map { |presented|
17
+ new(presented, in_collection: true)
18
+ }
19
+ end
20
+ end
21
+
22
+ def initialize(presented, in_collection: false)
23
+ @presented = presented
24
+ @in_collection = in_collection
25
+ end
26
+
27
+ def in_collection?
28
+ @in_collection == true
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class EntityClassPresenter < Presenter
4
+ def to_hash
5
+ {
6
+ :class => presented.name,
7
+ :count => presented.count,
8
+ :registered_processes => registered_processes
9
+ }
10
+ end
11
+
12
+ def registered_processes
13
+ presented.processes.map { |name, attributes|
14
+ attributes.merge(:name => name)
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class EntityPresenter < Presenter
4
+ def to_hash
5
+ {
6
+ :identifier => presented.identifier,
7
+ :name => presented.to_s,
8
+ :process_count => presented.processes.count
9
+ }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class ErrorPresenter < Presenter
4
+ def to_hash
5
+ {
6
+ :process_id => presented.wfid,
7
+ :expression_id => presented.expression.expid,
8
+ :error_class_name => presented.error_class_name,
9
+ :message => presented.message,
10
+ :backtrace => presented.backtrace
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class ExpressionPresenter < Presenter
4
+ def to_hash
5
+ hash = {
6
+ :process_id => presented.process.id,
7
+ :expression_id => presented.expid,
8
+ :tree => presented.tree
9
+ }
10
+ if presented.error
11
+ hash.merge!(:error => ErrorPresenter.present(presented.error).to_hash)
12
+ end
13
+ hash
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class ProcessPresenter < Presenter
4
+ def to_hash
5
+ {
6
+ :id => presented.id,
7
+ :definition_name => presented.definition_name,
8
+ :subscribed_events => presented.subscribed_events,
9
+ :entity_name => presented.entity_name
10
+ }.merge(extras)
11
+ end
12
+
13
+ def extras
14
+ if in_collection?
15
+ {}
16
+ else
17
+ {
18
+ :original_tree => presented.original_tree
19
+ }.merge(entity_hash)
20
+ end
21
+ end
22
+
23
+ def entity_hash
24
+ if presented.entity
25
+ { :entity => EntityPresenter.present(presented.entity).to_hash }
26
+ else
27
+ {}
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class TaskPresenter < Presenter
4
+ def to_hash
5
+ {
6
+ :id => presented.id,
7
+ :name => presented.to_s,
8
+ :role => presented.role,
9
+ :claimant => presented.claimant,
10
+ :process_id => presented.wfid
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class TrackerPresenter < Presenter
4
+ def to_hash
5
+ {
6
+ :id => presented.id,
7
+ :waiting_expression => presented.waiting_expression,
8
+ :original_hash => presented.original_hash,
9
+ :action => presented.action,
10
+ :conditions => presented.conditions,
11
+ :process_id => presented.wfid
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module Bumbleworks
2
+ module Api
3
+ class WorkerPresenter < Presenter
4
+ def to_hash
5
+ {
6
+ :id => presented.id,
7
+ :pid => presented.pid,
8
+ :name => presented.name,
9
+ :state => presented.state,
10
+ :ip => presented.ip,
11
+ :hostname => presented.hostname,
12
+ :system => presented.system,
13
+ :launched_at => presented.launched_at,
14
+ :updated_at => presented.updated_at,
15
+ :worker_class => presented.worker_class_name,
16
+ :uptime => presented.uptime,
17
+ :uptime_in_words => uptime_in_words,
18
+ :processed_last_minute => presented.processed_last_minute,
19
+ :wait_time_last_minute => presented.wait_time_last_minute,
20
+ :processed_last_hour => presented.processed_last_hour,
21
+ :wait_time_last_hour => presented.wait_time_last_hour
22
+ }
23
+ end
24
+
25
+ def uptime_in_words
26
+ TimeSupport.seconds_to_units_in_words(presented.uptime)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ module TimeSupport
2
+ class << self
3
+ def seconds_to_units(seconds, include_zeros: false, round_seconds: true)
4
+ seconds = seconds.to_i if round_seconds
5
+ units = Hash[
6
+ [:days, :hours, :minutes, :seconds].zip(
7
+ [60, 60, 24].inject([seconds]) {|result, unitsize|
8
+ result.unshift(*result.shift.divmod(unitsize))
9
+ result
10
+ }
11
+ )
12
+ ]
13
+ units.reject! { |k,v| v == 0 } unless include_zeros
14
+ units
15
+ end
16
+
17
+ def seconds_to_units_in_words(seconds, **options)
18
+ seconds_to_units(seconds, options).map { |unit, num|
19
+ "#{num} #{unit}"
20
+ }.join(', ')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module Bumbleworks
2
+ module Api
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ ENV['RORY_ENV'] ||= ENV['RACK_ENV'] || 'development'
2
+
3
+ require 'bundler/setup'
4
+ require 'bumbleworks/api'
5
+ require_relative 'spec/fixtures/bumbleworks_config'
6
+ require_relative 'spec/support/process_helpers'
7
+ include ProcessHelpers
8
+
9
+ Bumbleworks.start_worker!
10
+
11
+ Widget.truncate!
12
+ WidgetyFidget.truncate!
13
+
14
+ widget_processes = 20.times.collect do |i|
15
+ Widget.new(i).launch_process('task_process')
16
+ end
17
+
18
+ 5.times do |i|
19
+ WidgetyFidget.new(i)
20
+ end
21
+
22
+ wp = Bumbleworks.launch!('waiting_process')
23
+
24
+ ProcessHelpers.wait_until(:timeout => 30) do
25
+ wp.reload.trackers.count == 4
26
+ end
27
+
28
+ widget_processes.first(3).each do |p|
29
+ p.tasks.map(&:complete)
30
+ end
@@ -0,0 +1,32 @@
1
+ describe Bumbleworks::Api::EntitiesController do
2
+ describe "#types" do
3
+ it "returns entity types" do
4
+ get "/entities"
5
+ expect(last_response.body).to eq(
6
+ json_presentation_of(Bumbleworks.entity_classes)
7
+ )
8
+ end
9
+ end
10
+
11
+ describe "#index" do
12
+ it "returns all existing instances of a given entity type" do
13
+ foo, bar, baz = Widget.new('foo'),
14
+ Widget.new('bar'),
15
+ WidgetyFidget.new('baz')
16
+ get "/entities/widget"
17
+ expect(last_response.body).to eq(
18
+ json_presentation_of([foo, bar], :as => 'Entity')
19
+ )
20
+ end
21
+ end
22
+
23
+ describe "#show" do
24
+ it "returns requested instance" do
25
+ foo = Widget.new(130)
26
+ get "/entities/widget/130"
27
+ expect(last_response.body).to eq(
28
+ json_presentation_of(foo, :as => 'Entity')
29
+ )
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ describe Bumbleworks::Api::ErrorsController do
2
+ let(:process) { Bumbleworks.launch!('error_process') }
3
+ let(:error) { Bumbleworks.errors.first }
4
+ before(:each) do
5
+ wait_until { process.reload.errors.count > 0 }
6
+ end
7
+
8
+ describe "#index" do
9
+ it "returns all errors" do
10
+ Bumbleworks.launch!('error_process')
11
+ wait_until { Bumbleworks.errors.count == 2 }
12
+ get "/errors"
13
+ expect(last_response.body).to eq(
14
+ json_presentation_of(Bumbleworks.errors, :as => 'Error')
15
+ )
16
+ end
17
+ end
18
+
19
+ describe "#show" do
20
+ it "returns requested error" do
21
+ get "/processes/#{process.id}/expressions/#{error.expression.expid}/error"
22
+ expect(last_response.body).to eq(
23
+ json_presentation_of(error, :as => 'Error')
24
+ )
25
+ end
26
+ end
27
+
28
+ describe "#replay" do
29
+ after(:each) do
30
+ NaughtyParticipant.naughty_is_ok = false
31
+ end
32
+
33
+ it "attempts to replay error and returns success" do
34
+ NaughtyParticipant.naughty_is_ok = true
35
+ put "/processes/#{process.id}/expressions/#{error.expression.expid}/error/replay"
36
+ wait_until { !process.reload.running? }
37
+ expect(last_response.body).to eq(
38
+ { :status => 'replayed' }.to_json
39
+ )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ describe Bumbleworks::Api::ExpressionsController do
2
+ let(:process) { Bumbleworks.launch!('task_process') }
3
+ let(:expression) { process.expression_at_position('0_0_1') }
4
+ before(:each) do
5
+ wait_until { process.reload.tasks.count == 2 }
6
+ end
7
+
8
+ describe "#show" do
9
+ it "returns requested expression" do
10
+ get "/processes/#{process.id}/expressions/#{expression.expid}"
11
+ expect(last_response.body).to eq(
12
+ json_presentation_of(expression)
13
+ )
14
+ end
15
+ end
16
+
17
+ describe "#cancel" do
18
+ it "attempts to cancel expression and returns success" do
19
+ allow_any_instance_of(described_class).to receive(:expression).
20
+ and_return(expression)
21
+ expect(expression).to receive(:cancel!)
22
+ delete "/processes/#{process.id}/expressions/#{expression.expid}/cancel"
23
+ expect(last_response.body).to eq(
24
+ { :status => 'cancelled' }.to_json
25
+ )
26
+ end
27
+ end
28
+
29
+ describe "#kill" do
30
+ it "attempts to kill expression and returns success" do
31
+ allow_any_instance_of(described_class).to receive(:expression).
32
+ and_return(expression)
33
+ expect(expression).to receive(:kill!)
34
+ delete "/processes/#{process.id}/expressions/#{expression.expid}/kill"
35
+ expect(last_response.body).to eq(
36
+ { :status => 'killed' }.to_json
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ describe Bumbleworks::Api::ProcessesController do
2
+ describe "#index" do
3
+ before(:each) do
4
+ allow(Bumbleworks::Api::ProcessPresenter).to receive(:present).
5
+ with(:processes).
6
+ and_return(:presented_processes)
7
+ end
8
+
9
+ it "returns requested page with requested number of processes" do
10
+ allow(Bumbleworks::Process).to receive(:all).
11
+ with(:limit => 12, :offset => 24).
12
+ and_return(:processes)
13
+ get "/processes?page=3&limit=12"
14
+ expect(last_response.body).to eq(
15
+ json_presentation_of(:processes, :as => 'Process')
16
+ )
17
+ end
18
+
19
+ it "defaults to limit 10" do
20
+ allow(Bumbleworks::Process).to receive(:all).
21
+ with(:limit => 10, :offset => 10).
22
+ and_return(:processes)
23
+ get "/processes?page=2"
24
+ expect(last_response.body).to eq(
25
+ json_presentation_of(:processes, :as => 'Process')
26
+ )
27
+ end
28
+
29
+ it "defaults to page 1" do
30
+ allow(Bumbleworks::Process).to receive(:all).
31
+ with(:limit => 3, :offset => 0).
32
+ and_return(:processes)
33
+ get "/processes?limit=3"
34
+ expect(last_response.body).to eq(
35
+ json_presentation_of(:processes, :as => 'Process')
36
+ )
37
+ end
38
+ end
39
+
40
+ describe "#show" do
41
+ it "returns requested instance" do
42
+ process = Bumbleworks.launch!('task_process', :entity => Widget.new(41))
43
+ wait_until { process.tasks.count == 2 }
44
+ get "/processes/#{process.id}"
45
+ expect(last_response.body).to eq(
46
+ json_presentation_of(process)
47
+ )
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,82 @@
1
+ describe Bumbleworks::Api::TasksController do
2
+ let(:process) { Bumbleworks.launch!('task_process') }
3
+ before(:each) do
4
+ wait_until do
5
+ process.tasks.count == 2
6
+ end
7
+ end
8
+
9
+ describe "#index" do
10
+ it "returns all tasks" do
11
+ tasks = process.tasks.all
12
+ get "/tasks"
13
+ expect(last_response.body).to eq(
14
+ json_presentation_of(tasks)
15
+ )
16
+ end
17
+ end
18
+
19
+ describe "#show" do
20
+ it "returns requested task" do
21
+ task = process.tasks.first
22
+ get "/tasks/#{task.id}"
23
+ expect(last_response.body).to eq(
24
+ json_presentation_of(task)
25
+ )
26
+ end
27
+ end
28
+
29
+ describe "#claim" do
30
+ it "claims and returns task" do
31
+ task = process.tasks.first
32
+ put "/tasks/#{task.id}/claim", :claimant => "horatio"
33
+ expect(task.reload.claimant).to eq("horatio")
34
+ expect(last_response.body).to eq(
35
+ json_presentation_of(task)
36
+ )
37
+ end
38
+ end
39
+
40
+ describe "#release" do
41
+ it "releases and returns task" do
42
+ task = process.tasks.first
43
+ task.claim("whiskey")
44
+ put "/tasks/#{task.id}/release"
45
+ expect(task.reload.claimant).to be_nil
46
+ expect(last_response.body).to eq(
47
+ json_presentation_of(task)
48
+ )
49
+ end
50
+ end
51
+
52
+ describe "#complete" do
53
+ let(:task) { process.tasks.first }
54
+ before(:each) do
55
+ allow(Bumbleworks::Task).to receive(:find_by_id).
56
+ with(task.id).
57
+ and_return(task)
58
+ end
59
+
60
+ it "completes task and returns success" do
61
+ expect(task).to receive(:complete).with(:foo => "bar", "foo" => "bar")
62
+ put "/tasks/#{task.id}/complete", :foo => :bar
63
+ expect(last_response.body).to eq(
64
+ { :status => 'completed' }.to_json
65
+ )
66
+ end
67
+
68
+ it "returns 422 with error message if task not completable" do
69
+ allow(task).to receive(:complete).and_raise(
70
+ Bumbleworks::Task::NotCompletable, "uh oh you can't do that"
71
+ )
72
+ put "/tasks/#{task.id}/complete", :foo => :bar
73
+ expect(last_response.body).to eq(
74
+ {
75
+ :status => "not_completable",
76
+ :message => "uh oh you can't do that"
77
+ }.to_json
78
+ )
79
+ expect(last_response.status).to eq(422)
80
+ end
81
+ end
82
+ end