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.
@@ -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