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,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