farscape 1.2.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +14 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +192 -0
- data/LICENSE.md +19 -0
- data/README.md +189 -0
- data/Rakefile +10 -0
- data/WRITING_PLUGINS.md +162 -0
- data/lib/farscape/agent.rb +113 -0
- data/lib/farscape/base_agent.rb +15 -0
- data/lib/farscape/cache.rb +13 -0
- data/lib/farscape/client/base_client.rb +27 -0
- data/lib/farscape/client/http_client.rb +99 -0
- data/lib/farscape/clients.rb +9 -0
- data/lib/farscape/errors.rb +172 -0
- data/lib/farscape/helpers/partially_ordered_list.rb +75 -0
- data/lib/farscape/logger.rb +13 -0
- data/lib/farscape/plugins.rb +71 -0
- data/lib/farscape/representor.rb +110 -0
- data/lib/farscape/transition.rb +81 -0
- data/lib/farscape/version.rb +6 -0
- data/lib/farscape.rb +4 -0
- data/lib/plugins/plugins.rb +104 -0
- data/spec/lib/farscape/cache_spec.rb +22 -0
- data/spec/lib/farscape/integration/entry_point_spec.rb +29 -0
- data/spec/lib/farscape/integration/interface_spec.rb +222 -0
- data/spec/lib/farscape/integration/representor_spec.rb +36 -0
- data/spec/lib/farscape/integration/resource_crud_spec.rb +92 -0
- data/spec/lib/farscape/logger_spec.rb +23 -0
- data/spec/lib/farscape/plugins_spec.rb +344 -0
- data/spec/lib/farscape/transition_spec.rb +79 -0
- data/spec/lib/helpers/partially_ordered_list_spec.rb +125 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/helpers.rb +5 -0
- data/tasks/yard.rake +15 -0
- metadata +221 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
require 'active_support/core_ext/string/filters'
|
4
|
+
|
5
|
+
describe Farscape::RepresentorAgent do
|
6
|
+
let(:entry_point) { "http://localhost:#{RAILS_PORT}"}
|
7
|
+
|
8
|
+
let(:drds_link) { Farscape::Agent.new(entry_point).enter.transitions["drds"] }
|
9
|
+
let(:can_do_hash) { {conditions: 'can_do_anything'} }
|
10
|
+
let(:name) { 'Brave New DRD' }
|
11
|
+
let(:attrs) { {name: name, status: 'activated', old_status: 'activated'} }
|
12
|
+
|
13
|
+
context 'can do anything' do
|
14
|
+
|
15
|
+
it 'includes appropriate transitions' do
|
16
|
+
drds_resource = drds_link.invoke { |req| req.parameters = can_do_hash }
|
17
|
+
expect(drds_resource.transitions.keys).to include('create')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can create a drd' do
|
21
|
+
name = 'Brave New DRD'
|
22
|
+
drds_resource = drds_link.invoke { |req| req.parameters = can_do_hash }
|
23
|
+
drd = drds_resource.transitions['create'].invoke do |req|
|
24
|
+
req.attributes = attrs
|
25
|
+
req.parameters = can_do_hash
|
26
|
+
end
|
27
|
+
expect(drd.transitions['self'].invoke.attributes['name']).to eq(name)
|
28
|
+
drd.transitions['delete'].invoke # Cleanup, failure here should imply failure in 'can delete a drd'
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'an existing drd' do
|
32
|
+
before do
|
33
|
+
drds_resource = drds_link.invoke { |req| req.parameters = can_do_hash }
|
34
|
+
@drd = drds_resource.transitions['create'].invoke do |req|
|
35
|
+
req.attributes = attrs
|
36
|
+
req.parameters = can_do_hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
it 'can delete a drd' do
|
42
|
+
# NB We compare attributes as the self link will differ between the two calls
|
43
|
+
recall_drds = ->() { @drd.transitions['self'].invoke { |r| r.parameters = can_do_hash }.to_hash[:attributes] }
|
44
|
+
drd_attributes = @drd.to_hash[:attributes]
|
45
|
+
self_attributes = recall_drds.call
|
46
|
+
expect(self_attributes).to eq(drd_attributes)
|
47
|
+
@drd.transitions['delete'].invoke
|
48
|
+
expect { recall_drds.call }.to raise_error( Farscape::Exceptions::NotFound )
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns proper errors' do
|
52
|
+
# NB We compare attributes as the self link will differ between the two calls
|
53
|
+
recall_drds = ->() { @drd.transitions['self'].invoke { |r| r.parameters = can_do_hash }.to_hash[:attributes] }
|
54
|
+
drd_attributes = @drd.to_hash[:attributes]
|
55
|
+
@drd.transitions['delete'].invoke
|
56
|
+
begin
|
57
|
+
recall_drds.call
|
58
|
+
raise Exception.new('Moya responded with a resource that\'s been deleted')
|
59
|
+
rescue Farscape::Exceptions::NotFound => e
|
60
|
+
expect(e.error_description).to eq('The server has not found anything matching the Request-URI.
|
61
|
+
No indication is given of whether the condition is temporary or permanent.
|
62
|
+
This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.'.squish)
|
63
|
+
expect(e.representor.to_hash).to eq(e.message)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'can update a drd' do
|
68
|
+
new_kind = "sentinel"
|
69
|
+
begin
|
70
|
+
@drd.transitions['update'].invoke do |r|
|
71
|
+
r.attributes = {kind: new_kind}
|
72
|
+
r.parameters = can_do_hash
|
73
|
+
end
|
74
|
+
rescue
|
75
|
+
#TODO: Farscape handles redirects
|
76
|
+
end
|
77
|
+
kindof = @drd.transitions['self'].invoke.attributes['kind']
|
78
|
+
expect(kindof).to eq(new_kind)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'can toggle a drds activation state' do
|
82
|
+
status = @drd.attributes['status']
|
83
|
+
action = status == 'activated' ? 'deactivate' : 'activate'
|
84
|
+
@drd.transitions[action].invoke { |r| r.parameters = can_do_hash }
|
85
|
+
new_status = @drd.transitions['self'].invoke { |r| r.parameters = can_do_hash }.attributes['status']
|
86
|
+
expect(status).to_not eq(new_status)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'farscape/logger'
|
3
|
+
|
4
|
+
describe(Farscape) do
|
5
|
+
|
6
|
+
describe 'logger' do
|
7
|
+
|
8
|
+
after(:each) { Farscape.logger = nil }
|
9
|
+
|
10
|
+
it 'defaults to the built-in Ruby logger' do
|
11
|
+
Farscape.logger = nil
|
12
|
+
expect(Farscape.logger).to be_a(::Logger)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can be set to any kind of logger' do
|
16
|
+
custom_logger = Object.new
|
17
|
+
Farscape.logger = custom_logger
|
18
|
+
expect(Farscape.logger).to eq(custom_logger)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,344 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module TestMiddleware
|
4
|
+
class NoGetNoProblem
|
5
|
+
def initialize(app, config = {})
|
6
|
+
@app = app
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
def call(env)
|
10
|
+
@app.call(env).on_complete do |env|
|
11
|
+
unless @config[:permissive]
|
12
|
+
raise StandardError, "Shazam!" if env[:method] == :get
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Saboteur
|
19
|
+
def initialize(app, *)
|
20
|
+
@app = app
|
21
|
+
end
|
22
|
+
def call(env)
|
23
|
+
env[:sabotaged] = true
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class SabotageDetector
|
29
|
+
def initialize(app, *)
|
30
|
+
@app = app
|
31
|
+
end
|
32
|
+
def call(env)
|
33
|
+
raise 'Sabotage detected' if env[:sabotaged]
|
34
|
+
@app.call(env)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe Farscape::Agent do
|
41
|
+
|
42
|
+
before(:each) do
|
43
|
+
Farscape.clear
|
44
|
+
detector_plugin = {name: :Detector, type: :sebacean, middleware: [TestMiddleware::SabotageDetector], default_state: :disabled}
|
45
|
+
saboteur_middleware = {class: TestMiddleware::Saboteur, before: :sebacean}
|
46
|
+
saboteur_plugin = {name: :Saboteur, type: :scarran, middleware: [saboteur_middleware]}
|
47
|
+
Farscape.register_plugin(detector_plugin)
|
48
|
+
Farscape.register_plugin(saboteur_plugin)
|
49
|
+
end
|
50
|
+
|
51
|
+
after(:all) { Farscape.clear }
|
52
|
+
|
53
|
+
let(:entry_point) { "http://localhost:#{RAILS_PORT}"}
|
54
|
+
let(:can_do_hash) { {conditions: 'can_do_anything'} }
|
55
|
+
|
56
|
+
it 'accepts a using directive that enables a plugin' do
|
57
|
+
expect { Farscape::Agent.new(entry_point).using(:Detector).enter }.to raise_error('Sabotage detected')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'accepts an omitting directive that disables a plugin' do
|
61
|
+
agent = Farscape::Agent.new(entry_point).using(:Detector)
|
62
|
+
agent = agent.omitting(:sebacean)
|
63
|
+
expect { agent.enter }.to_not raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'allows showing the list of registered plugins' do
|
67
|
+
expect(Farscape::Agent.new(entry_point).plugins).to eq(Farscape.plugins)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'allows showing only those enabled for the agent' do
|
71
|
+
agent = Farscape::Agent.new(entry_point).using(:Detector)
|
72
|
+
expect(agent.enabled_plugins[:Detector]).to_not be_nil
|
73
|
+
expect(Farscape.disabled_plugins[:Detector]).to_not be_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'allows showing only those disabled for the agent' do
|
77
|
+
agent = Farscape::Agent.new(entry_point).using(:Detector)
|
78
|
+
agent = agent.omitting(:sebacean)
|
79
|
+
expect(agent.disabled_plugins.keys).to eq([:Detector])
|
80
|
+
expect(Farscape.enabled_plugins.keys).to eq([:Saboteur])
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'allows using on TransitionAgent objects' do
|
84
|
+
transition = Farscape::Agent.new(entry_point).enter.transitions["drds"].using(:Detector)
|
85
|
+
expect { transition.invoke { |req| req.parameters = can_do_hash } }.to raise_error('Sabotage detected')
|
86
|
+
expect(transition.disabled_plugins).to eq({})
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'allows using on RepresentorAgent objects' do
|
90
|
+
representor = Farscape::Agent.new(entry_point).enter.using(:Detector)
|
91
|
+
transition = representor.transitions["drds"]
|
92
|
+
expect { transition.invoke { |req| req.parameters = can_do_hash } }.to raise_error('Sabotage detected')
|
93
|
+
expect(representor.disabled_plugins).to eq({})
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
describe Farscape do
|
99
|
+
after(:all) { Farscape.clear }
|
100
|
+
|
101
|
+
context 'configuring plugins' do
|
102
|
+
|
103
|
+
before(:each) { Farscape.clear }
|
104
|
+
|
105
|
+
it 'can register a plugin' do
|
106
|
+
plugin = {name: :Peacekeeper, type: :sebacean}
|
107
|
+
expect(Farscape.register_plugin(plugin)).to be
|
108
|
+
expect(Farscape.plugins).to eq({Peacekeeper: plugin})
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'can preemptively disable a plugin by name' do
|
112
|
+
Farscape.disable!(:Peacekeeper)
|
113
|
+
plugin = {name: :Peacekeeper, type: :sebacean}
|
114
|
+
Farscape.register_plugin(plugin)
|
115
|
+
expect(Farscape.enabled_plugins).to be_empty
|
116
|
+
expect(Farscape.disabled?(plugin)).to be true
|
117
|
+
expect(Farscape.enabled?(plugin)).to be false
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'can preemptively disable a plugin by type' do
|
121
|
+
Farscape.disable!(:sebacean)
|
122
|
+
plugin = {name: :Peacekeeper, type: :sebacean}
|
123
|
+
Farscape.register_plugin(plugin)
|
124
|
+
expect(Farscape.enabled_plugins).to be_empty
|
125
|
+
expect(Farscape.disabled?(plugin)).to be true
|
126
|
+
expect(Farscape.enabled?(plugin)).to be false
|
127
|
+
end
|
128
|
+
|
129
|
+
it "can disable a plugin after it's added" do
|
130
|
+
Farscape.register_plugin(name: :Peacekeeper, type: :sebacean)
|
131
|
+
Farscape.register_plugin(name: :Imperium, type: :scarran)
|
132
|
+
Farscape.disable!(:sebacean)
|
133
|
+
expect(Farscape.enabled_plugins).to be_one
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'adding middleware' do
|
137
|
+
|
138
|
+
before(:each) { Farscape.clear }
|
139
|
+
after(:all) { Farscape.clear }
|
140
|
+
|
141
|
+
it 'can add middleware' do
|
142
|
+
plugin = {name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}
|
143
|
+
Farscape.register_plugin(plugin)
|
144
|
+
|
145
|
+
expect(Farscape.middleware_stack.map{ |elt| elt[:class] } ).to eq([TestMiddleware::NoGetNoProblem])
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'removes middleware when the source plugin is disabled' do
|
149
|
+
plugin = {name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}
|
150
|
+
Farscape.register_plugin(plugin)
|
151
|
+
Farscape.disable!( :sebacean )
|
152
|
+
|
153
|
+
expect(Farscape.middleware_stack).to be_none
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'uses the middleware when making requests' do
|
157
|
+
plugin = {name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}
|
158
|
+
Farscape.register_plugin(plugin)
|
159
|
+
|
160
|
+
expect{Farscape::Agent.new("http://localhost:#{RAILS_PORT}").enter}.to raise_error('Shazam!')
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'can configure middleware' do
|
164
|
+
plugin = {name: :Peacekeeper, type: :sebacean, middleware: [{class: TestMiddleware::NoGetNoProblem, config: {permissive: true}}]}
|
165
|
+
Farscape.register_plugin(plugin)
|
166
|
+
|
167
|
+
expect{Farscape::Agent.new("http://localhost:#{RAILS_PORT}").enter}.not_to raise_error
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'adds middleware to disabled when disabled' do
|
171
|
+
plugin = {name: :Peacekeeper, default_state: :disabled, type: :sebacean, middleware: [{class: TestMiddleware::NoGetNoProblem, config: {permissive: true}}]}
|
172
|
+
Farscape.register_plugin(plugin)
|
173
|
+
|
174
|
+
expect(Farscape.enabled_plugins).to eq({})
|
175
|
+
expect(Farscape.disabled_plugins).to eq(plugin[:name] => plugin)
|
176
|
+
end
|
177
|
+
|
178
|
+
[:sebacean, TestMiddleware::SabotageDetector,'TestMiddleware::SabotageDetector',['TestMiddleware::SabotageDetector']].each do |form|
|
179
|
+
it "honors the before: option in middleware when given as #{form.inspect}" do
|
180
|
+
saboteur_middleware = {class: TestMiddleware::Saboteur, before: form}
|
181
|
+
saboteur_plugin = {name: :Saboteur, type: :scarran, middleware: [saboteur_middleware]}
|
182
|
+
detector_plugin = {name: :Detector, type: :sebacean, middleware: [TestMiddleware::SabotageDetector]}
|
183
|
+
[saboteur_plugin, detector_plugin].shuffle.each { |plugin| Farscape.register_plugin(plugin) }
|
184
|
+
|
185
|
+
expect(Farscape.middleware_stack.map{ |m| m[:class] }).to eq( [TestMiddleware::Saboteur, TestMiddleware::SabotageDetector] )
|
186
|
+
expect{Farscape::Agent.new("http://localhost:#{RAILS_PORT}").enter}.to raise_error('Sabotage detected')
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
[:sebacean, TestMiddleware::SabotageDetector,'TestMiddleware::SabotageDetector',['TestMiddleware::SabotageDetector']].each do |form|
|
191
|
+
it "honors the after: option in middleware when given as #{form.inspect}" do
|
192
|
+
saboteur_middleware = {class: TestMiddleware::Saboteur, after: form}
|
193
|
+
saboteur_plugin = {name: :Saboteur, type: :scarran, middleware: [saboteur_middleware]}
|
194
|
+
detector_plugin = {name: :Detector, type: :sebacean, middleware: [TestMiddleware::SabotageDetector]}
|
195
|
+
[saboteur_plugin, detector_plugin].shuffle.each { |plugin| Farscape.register_plugin(plugin) }
|
196
|
+
|
197
|
+
expect(Farscape.middleware_stack.map{ |m| m[:class] }).to eq( [TestMiddleware::SabotageDetector, TestMiddleware::Saboteur] )
|
198
|
+
expect{Farscape::Agent.new("http://localhost:#{RAILS_PORT}").enter}.not_to raise_error
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'doesn\'t disable everything with one' do
|
203
|
+
detector_plugin = {name: :Detector, type: :sebacean, middleware: [TestMiddleware::SabotageDetector], default_state: :disabled}
|
204
|
+
saboteur_middleware = {class: TestMiddleware::Saboteur}
|
205
|
+
saboteur_plugin = {name: :Saboteur, type: :scarran, middleware: [saboteur_middleware]}
|
206
|
+
Farscape.register_plugin(detector_plugin)
|
207
|
+
Farscape.register_plugin(saboteur_plugin)
|
208
|
+
|
209
|
+
expect(Farscape.enabled_plugins.keys).to eq([:Saboteur])
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'doesn\'t allow sneaky enabling' do
|
213
|
+
detector_plugin = {name: :Detector, type: :sebacean, middleware: [TestMiddleware::SabotageDetector], default_state: :disabled}
|
214
|
+
Farscape.register_plugin(detector_plugin)
|
215
|
+
|
216
|
+
expect(Farscape.disabled_plugins.keys).to eq([:Detector])
|
217
|
+
|
218
|
+
detector_plugin = {name: :Detector, type: :sebacean, middleware: [TestMiddleware::SabotageDetector], default_state: :enabled}
|
219
|
+
Farscape.register_plugin(detector_plugin)
|
220
|
+
|
221
|
+
expect(Farscape.disabled_plugins.keys).to eq([:Detector])
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'extensions' do
|
227
|
+
before(:each) { Farscape.clear }
|
228
|
+
after(:all) { Farscape.clear }
|
229
|
+
|
230
|
+
let(:entry_point) { "http://localhost:#{RAILS_PORT}"}
|
231
|
+
let(:can_do_hash) { {conditions: 'can_do_anything'} }
|
232
|
+
|
233
|
+
it 'allows extensions' do
|
234
|
+
module Peacekeeper
|
235
|
+
def pacify!
|
236
|
+
raise 'none shall pass' unless enabled_plugins.include?(:Saboteur)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
Farscape.register_plugin(name: :Peacekeeper, type: :security, extensions: {Agent: [Peacekeeper]})
|
240
|
+
expect { Farscape::Agent.new.pacify! }.to raise_error('none shall pass')
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'allows extensions with altering existing methods' do
|
244
|
+
module Peacemaker
|
245
|
+
def self.extended(base)
|
246
|
+
base.instance_eval do
|
247
|
+
@original_transitions = method(:transitions)
|
248
|
+
def transitions
|
249
|
+
raise 'none shall pass' if @original_transitions.call.keys.include?("drds")
|
250
|
+
@original_transitions.call
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
Farscape.register_plugin(name: :Peacekeeper, type: :security, extensions: {RepresentorAgent: [Peacemaker]})
|
256
|
+
expect { Farscape::Agent.new.enter(entry_point).transitions.keys }.to raise_error('none shall pass')
|
257
|
+
expect(Farscape::Agent.new.enter(entry_point).omitting(:Peacekeeper).transitions.keys).to include("drds")
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'allows discovery extensions' do
|
261
|
+
module ServiceCatalogue
|
262
|
+
def self.extended(base)
|
263
|
+
base.instance_eval do
|
264
|
+
@original_enter = method(:enter)
|
265
|
+
@dispatches = {
|
266
|
+
:moya => "http://localhost:#{RAILS_PORT}"
|
267
|
+
}
|
268
|
+
def enter(entry=nil)
|
269
|
+
@entry_point ||= entry
|
270
|
+
@entry_point = @dispatches[@entry_point] || @entry_point
|
271
|
+
@original_enter.call
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
Farscape.register_plugin(name: :Wormlet, type: :discovery, extensions: {Agent: [ServiceCatalogue]})
|
277
|
+
expect(Farscape::Agent.new(:moya).enter.transitions.keys).to include("drds")
|
278
|
+
expect(Farscape::Agent.new.enter(:moya).transitions.keys).to include("drds")
|
279
|
+
expect { Farscape::Agent.new.omitting(:discovery).enter(:moya).transitions.keys }.to raise_error(NoMethodError)
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
context 'workflow' do
|
285
|
+
|
286
|
+
before(:each) { Farscape.clear }
|
287
|
+
|
288
|
+
let(:entry_point) { "http://localhost:#{RAILS_PORT}"}
|
289
|
+
let(:can_do_hash) { {conditions: 'can_do_anything'} }
|
290
|
+
|
291
|
+
it 'manages enabling and disabling plugins' do
|
292
|
+
registration = [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
293
|
+
registration_keys = registration.map { |plugin| plugin[:name] }
|
294
|
+
Farscape.register_plugins(registration)
|
295
|
+
|
296
|
+
expect(Farscape.plugins.keys).to eq( registration_keys )
|
297
|
+
expect(Farscape.enabled_plugins.keys).to eq( registration_keys )
|
298
|
+
expect(Farscape.disabled_plugins.keys).to eq([])
|
299
|
+
|
300
|
+
Farscape.disable!(type: :sebacean)
|
301
|
+
|
302
|
+
expect(Farscape.enabled_plugins.keys).to eq([])
|
303
|
+
expect(Farscape.disabled_plugins.keys).to eq( registration_keys )
|
304
|
+
|
305
|
+
Farscape.enable!(name: :Peacekeeper)
|
306
|
+
|
307
|
+
expect(Farscape.enabled_plugins.keys).to eq( registration_keys )
|
308
|
+
expect(Farscape.disabled_plugins.keys).to eq([])
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'managed complex enabling and disabling on instances' do
|
312
|
+
registration = [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
313
|
+
registration_keys = registration.map { |plugin| plugin[:name] }
|
314
|
+
Farscape.register_plugins(registration)
|
315
|
+
peacekeeper_plugin = {:Peacekeeper=>{:name=>:Peacekeeper, :type=>:sebacean, :middleware=>[TestMiddleware::NoGetNoProblem], :enabled=>true}}
|
316
|
+
disabled_plugin = {:Peacekeeper=>{:name=>:Peacekeeper, :type=>:sebacean, :middleware=>[TestMiddleware::NoGetNoProblem], :enabled=>false}}
|
317
|
+
|
318
|
+
expect(Farscape.enabled_plugins).to eq(peacekeeper_plugin)
|
319
|
+
|
320
|
+
agent = Farscape::Agent.new.omitting(name: :Peacekeeper)
|
321
|
+
|
322
|
+
expect(agent.plugins).to eq(disabled_plugin)
|
323
|
+
expect(agent.enabled_plugins).to eq({})
|
324
|
+
expect(agent.disabled_plugins).to eq(disabled_plugin)
|
325
|
+
|
326
|
+
resource = agent.enter(entry_point).transitions["drds"].invoke { |builder| builder.parameters = can_do_hash }
|
327
|
+
expect(resource.plugins).to eq(disabled_plugin)
|
328
|
+
expect(resource.enabled_plugins).to eq({})
|
329
|
+
expect(resource.plugins).to eq(disabled_plugin)
|
330
|
+
|
331
|
+
transition = resource.using(name: :Peacekeeper).transitions["items"]
|
332
|
+
details = resource.using(name: :Peacekeeper).embedded["items"].first
|
333
|
+
|
334
|
+
expect { transition.invoke }.to raise_error('Shazam!')
|
335
|
+
expect(details.plugins).to eq(peacekeeper_plugin)
|
336
|
+
expect(details.enabled_plugins).to eq(peacekeeper_plugin)
|
337
|
+
expect(details.disabled_plugins).to eq({})
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Farscape::TransitionAgent do
|
4
|
+
let(:client) { double }
|
5
|
+
let(:agent) { double(media_type: :hale, client: client) }
|
6
|
+
let(:transition) { double(uri: 'com:mdsol', templated?: false) }
|
7
|
+
let(:arg) { { additional_fields: { key1: 'value1', key2: 'value2' } } }
|
8
|
+
let(:transition_agent) { described_class.new(transition, agent) }
|
9
|
+
let(:field_list) { double(name: 'additional_fields') }
|
10
|
+
let(:call_options) { { url: 'com:mdsol', headers: { Accept: 'application/vnd.hale+json' } } }
|
11
|
+
|
12
|
+
before do
|
13
|
+
allow(agent).to receive(:get_accept_header).and_return(Accept: 'application/vnd.hale+json')
|
14
|
+
allow(agent).to receive(:find_exception).and_return(nil)
|
15
|
+
allow(transition).to receive(:interface_method).and_return(http_method)
|
16
|
+
allow(transition).to receive(:parameters).and_return([ field_list ])
|
17
|
+
allow(transition).to receive(:attributes).and_return([])
|
18
|
+
allow_any_instance_of(described_class).to receive(:handle_extensions).and_return(nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'GET method' do
|
22
|
+
let(:http_method) { "get" }
|
23
|
+
|
24
|
+
it 'stores parameters in params' do
|
25
|
+
expect(client).to receive(:invoke).with(call_options.merge(method: http_method, params: arg))
|
26
|
+
transition_agent.invoke(arg)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'POST method' do
|
31
|
+
let(:http_method) { "post" }
|
32
|
+
|
33
|
+
it 'stores parameters in body' do
|
34
|
+
expect(client).to receive(:invoke).with(call_options.merge(method: http_method, body: arg))
|
35
|
+
transition_agent.invoke(arg)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# see https://tools.ietf.org/html/rfc6570#section-3.2.6
|
40
|
+
context "URL with a path segment" do
|
41
|
+
let(:transition) do
|
42
|
+
Representors::Transition.new(
|
43
|
+
templated: true,
|
44
|
+
rel: "find",
|
45
|
+
href: "https://example.com/api/v1/issues{/issue_uuid}"
|
46
|
+
)
|
47
|
+
end
|
48
|
+
let(:issue_uuid) { SecureRandom.uuid }
|
49
|
+
|
50
|
+
context "GET" do
|
51
|
+
let(:http_method) { "get" }
|
52
|
+
|
53
|
+
it "interpolates a templated URI" do
|
54
|
+
options = call_options.merge(
|
55
|
+
method: http_method,
|
56
|
+
url: "https://example.com/api/v1/issues/#{issue_uuid}",
|
57
|
+
params: arg
|
58
|
+
)
|
59
|
+
expect(client).to receive(:invoke).with(options)
|
60
|
+
transition_agent.invoke(arg.merge(issue_uuid: issue_uuid))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "POST" do
|
65
|
+
let(:http_method) { "post" }
|
66
|
+
|
67
|
+
it "does not interpolate a templated URI" do
|
68
|
+
params = arg.merge(issue_uuid: issue_uuid)
|
69
|
+
options = call_options.merge(
|
70
|
+
method: http_method,
|
71
|
+
url: "https://example.com/api/v1/issues",
|
72
|
+
body: params
|
73
|
+
)
|
74
|
+
expect(client).to receive(:invoke).with(options)
|
75
|
+
transition_agent.invoke(params)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require_relative '../../../lib/farscape/helpers/partially_ordered_list.rb'
|
2
|
+
|
3
|
+
describe(PartiallyOrderedList) do
|
4
|
+
|
5
|
+
it 'sorts a list given a complete ordering' do
|
6
|
+
list = described_class.new { |a,b| a <=> b }
|
7
|
+
1.upto(10) { |i| list.add(i) }
|
8
|
+
expect(list.to_a).to eq([*1..10])
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'sorts a shuffled list given a complete ordering' do
|
12
|
+
list = described_class.new { |a,b| a <=> b }
|
13
|
+
[*1..10].shuffle.each { |i| list.add(i) }
|
14
|
+
expect(list.to_a).to eq([*1..10])
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns an arbitrary list when nothing is ordered' do
|
18
|
+
list = described_class.new {}
|
19
|
+
(1..20).each { |i| list.add(i) }
|
20
|
+
expect(list.count).to eq(20)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
it 'finds an order for a list satisfying a disjoint ordering' do
|
25
|
+
list = described_class.new do |a,b|
|
26
|
+
if a % 2 == b % 2
|
27
|
+
a <=> b
|
28
|
+
end
|
29
|
+
end
|
30
|
+
[*1..20].shuffle.each { |i| list.add(i) }
|
31
|
+
expect(list.partition(&:odd?)).to eq([*1..20].partition(&:odd?))
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'finds one possible transitive ordering' do
|
35
|
+
list = described_class.new do |a,b|
|
36
|
+
if a % 2 == b % 2
|
37
|
+
a <=> b
|
38
|
+
elsif [a,b] == [1,8]
|
39
|
+
1
|
40
|
+
elsif [a,b] == [8,1]
|
41
|
+
-1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
[*1..20].shuffle.each { |i| list.add(i) }
|
45
|
+
expect(list.partition(&:odd?)).to eq([*1..20].partition(&:odd?))
|
46
|
+
expect(list.find_index(8)).to be < list.find_index(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'finds the only possible transitive ordering' do
|
50
|
+
list = described_class.new do |a,b|
|
51
|
+
if a % 2 == b % 2
|
52
|
+
a <=> b
|
53
|
+
elsif [a,b] == [1,20]
|
54
|
+
1
|
55
|
+
elsif [a,b] == [20,1]
|
56
|
+
-1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
[*1..20].shuffle.each { |i| list.add(i) }
|
60
|
+
expect(list.to_a).to eq([2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19])
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'raises on circular ordering' do
|
64
|
+
list = described_class.new do |a,b|
|
65
|
+
if [a,b] == [1,10]
|
66
|
+
1
|
67
|
+
elsif [a,b] == [10,1]
|
68
|
+
-1
|
69
|
+
else
|
70
|
+
a <=> b
|
71
|
+
end
|
72
|
+
end
|
73
|
+
[*1..10].shuffle.each { |i| list.add(i) }
|
74
|
+
expect{ list.to_a }.to raise_error(PartiallyOrderedList::CircularOrderingError)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'deletes' do
|
78
|
+
list = described_class.new { |a,b| a <=> b }
|
79
|
+
[*1..10].shuffle.each { |i| list.add(i) }
|
80
|
+
list.delete(5)
|
81
|
+
expect(list.to_a).to eq( [1,2,3,4,6,7,8,9,10] )
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'deletes when there is a cached ordering' do
|
85
|
+
list = described_class.new {}
|
86
|
+
list.add 1
|
87
|
+
list.add 2
|
88
|
+
list.to_a
|
89
|
+
list.delete 1
|
90
|
+
expect(list.to_a).to eq([2])
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'invalidates cached ordering when a new item is added' do
|
94
|
+
list = described_class.new {}
|
95
|
+
1.upto(10) { |i| list.add(i) }
|
96
|
+
list.to_a
|
97
|
+
list.add(11)
|
98
|
+
expect(list.count).to eq(11)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns an enumerator when each is called without a block' do
|
102
|
+
expect(described_class.new{}.each).to be_a(Enumerator)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'raises if new is called without a block' do
|
106
|
+
expect{described_class.new}.to raise_error(ArgumentError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 're-sorts as needed' do
|
110
|
+
list = described_class.new do |a,b|
|
111
|
+
if a % 2 == b % 2
|
112
|
+
a <=> b
|
113
|
+
elsif [a,b] == [1,20]
|
114
|
+
1
|
115
|
+
elsif [a,b] == [20,1]
|
116
|
+
-1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
[*1..19].shuffle.each { |i| list.add(i) }
|
120
|
+
list.to_a
|
121
|
+
list.add(20)
|
122
|
+
expect(list.to_a).to eq([2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19])
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|