farscape 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|