xfrtuc 0.0.13 → 1.0.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.
data/spec/xfrtuc_spec.rb CHANGED
@@ -1,372 +1,21 @@
1
- require 'securerandom'
2
- require 'spec_helper'
3
- require 'sham_rack'
1
+ # frozen_string_literal: true
4
2
 
5
- module Xfrtuc
6
- User = Struct.new(:name, :password)
7
- class FakeTransferatu
8
- attr_reader :groups
9
-
10
- def headers
11
- { content_type: 'application/json' }
12
- end
13
-
14
- def initialize(*users)
15
- @groups = []
16
- @transfers = {}
17
- @schedules = {}
18
- @users = users
19
- end
20
-
21
- def active_groups
22
- @groups.reject { |g| g[:deleted] }
23
- end
24
-
25
- def find_transfer(group_name, &block)
26
- @transfers[group_name].find(&block)
27
- end
28
-
29
- def last_transfer(group_name)
30
- @transfers[group_name].last
31
- end
32
-
33
- def last_schedule(group_name)
34
- @schedules[group_name].last
35
- end
36
-
37
- def add_group(name, log_url=nil)
38
- existing_group = @groups.find { |g| g[:name] == name }
39
- if existing_group
40
- if existing_group[:deleted]
41
- # undelete
42
- existing_group.delete(:deleted)
43
- [201, headers, [ existing_group.to_json ] ]
44
- else
45
- [409, headers, [ { id: :conflict, message: "group #{name} already exists" }.to_json ] ]
46
- end
47
- else
48
- group = { name: name, log_input_url: log_url }
49
- @groups << group
50
- @transfers[name] = []
51
- @schedules[name] = []
52
- [201, headers, [ group.to_json ] ]
53
- end
54
- end
55
-
56
- def delete_group(name)
57
- target = @groups.find { |g| g[:name] == name }
58
- if target && target[:deleted]
59
- [410, headers, []]
60
- elsif target.nil?
61
- [404, headers, []]
62
- else
63
- target[:deleted] = true
64
- [200, headers, [target.to_json]]
65
- end
66
- end
67
-
68
- def list_groups
69
- [200, headers, [@groups.to_json]]
70
- end
71
-
72
- def get_group(name)
73
- [200, headers, [@groups.find { |g| g[:name] == name }.to_json]]
74
- end
75
-
76
- def list_transfers(group_name)
77
- group = @groups.find { |g| g[:name] == group_name }
78
- if group.nil?
79
- [404, headers, []]
80
- elsif group[:deleted]
81
- [410, headers, []]
82
- else
83
- transfers = @transfers[group_name]
84
- [200, headers, [transfers.to_json]]
85
- end
86
- end
87
-
88
- def get_transfer(group_name, xfer_id, verbose=false)
89
- group = @groups.find { |g| g[:name] == group_name }
90
- if group.nil?
91
- [404, headers, []]
92
- elsif group[:deleted]
93
- [409, headers, []]
94
- else
95
- transfer = @transfers[group_name].find { |xfer| xfer[:uuid] == xfer_id }
96
- if verbose
97
- result = transfer.dup
98
- result[:logs] = []
99
- transfer = result
100
- end
101
- [200, headers, [transfer.to_json]]
102
- end
103
- end
104
-
105
- def add_transfer(group_name, transfer)
106
- unless @transfers.has_key? group_name
107
- return [404, headers, []]
108
- end
109
- xfer = { uuid: SecureRandom.uuid }
110
- %w(from_type from_url from_name to_type to_url to_name log_input_url).each do |key|
111
- xfer[key.to_sym] = transfer[key]
112
- end
113
- if transfer.has_key? 'num_keep'
114
- xfer[:num_keep] = transfer['num_keep']
115
- end
116
-
117
- @transfers[group_name] << xfer
118
- [201, {}, [xfer.to_json]]
119
- end
120
-
121
- def delete_transfer(group_name, xfer_id)
122
- unless @transfers.has_key? group_name
123
- return [404, headers, []]
124
- end
125
- xfer = @transfers[group_name].find { |item| item[:uuid] == xfer_id }
126
- if xfer.nil?
127
- return [404, headers, []]
128
- else
129
- @transfers[group_name].delete xfer
130
- return [200, headers, [ xfer.to_json ]]
131
- end
132
- end
133
-
134
- def take_transfer_action(action, args, group_name, xfer_id)
135
- unless @transfers.has_key? group_name
136
- return [404, headers, []]
137
- end
138
- xfer = @transfers[group_name].find { |item| item[:uuid] == xfer_id }
139
- if xfer.nil?
140
- return [404, headers, []]
141
- else
142
- case action
143
- when 'cancel' then
144
- now = Time.now
145
- xfer[:canceled_at] = now
146
- return [201, headers, [ { canceled_at: now }.to_json ]]
147
- when 'public-url' then
148
- expires_at = if args.has_key? 'ttl'
149
- Time.now + args['ttl'].to_i
150
- else
151
- Time.now + (10 * 60)
152
- end
153
- url = "https://example.com/backup/#{xfer[:uuid]}"
154
- return [201, headers, [ { url: url, expires_at: expires_at }.to_json ]]
155
- else
156
- return [404, headers, []]
157
- end
158
- end
159
- end
160
-
161
- def add_schedule(group_name, schedule)
162
- unless @schedules.has_key? group_name
163
- return [404, headers, []]
164
- end
165
- sched = { uuid: SecureRandom.uuid }
166
- %w(name callback_url days hour timezone).each do |key|
167
- sched[key.to_sym] = schedule[key]
168
- end
169
- %w(retain_weeks retain_months).each do |key|
170
- sched[key.to_sym] = schedule[key] if schedule.has_key? key
171
- end
172
- @schedules[group_name] << sched
173
- [201, {}, [sched.to_json]]
174
- end
175
-
176
- def delete_schedule(group_name, schedule_id)
177
- unless @schedules.has_key? group_name
178
- return [404, headers, []]
179
- end
180
- schedule = @schedules[group_name].find { |item| item[:uuid] == schedule_id }
181
- if schedule.nil?
182
- return [404, headers, []]
183
- else
184
- @schedules[group_name].delete schedule
185
- return [200, headers, [ schedule.to_json ]]
186
- end
187
- end
188
-
189
- def list_schedules(group_name)
190
- group = @groups.find { |g| g[:name] == group_name }
191
- if group.nil?
192
- [404, headers, []]
193
- elsif group[:deleted]
194
- [410, headers, []]
195
- else
196
- schedules = @schedules[group_name]
197
- [200, headers, [schedules.to_json]]
198
- end
199
- end
200
-
201
- def get_schedule(group_name, sched_id)
202
- group = @groups.find { |g| g[:name] == group_name }
203
- if group.nil?
204
- [404, headers, []]
205
- elsif group[:deleted]
206
- [410, headers, []]
207
- else
208
- sched = @schedules[group_name].find { |s| s[:uuid] == sched_id }
209
- if sched.nil?
210
- [404, headers, []]
211
- else
212
- [200, headers, [sched.to_json]]
213
- end
214
- end
215
- end
216
-
217
- def call(env)
218
- unless verify_auth(env)
219
- return [401, headers, ["Not authorized"]]
220
- end
221
- case path(env)
222
- when %r{/groups/[^/]+/transfers/[^/]+/actions} then
223
- transfers_actions_endpoint(env)
224
- when %r{/groups/[^/]+/transfers} then
225
- transfers_endpoint(env)
226
- when %r{/groups/[^/]+/schedules} then
227
- schedules_endpoint(env)
228
- when %r{/groups} then
229
- groups_endpoint(env)
230
- else
231
- [404, headers, []]
232
- end
233
- end
234
-
235
- def transfers_actions_endpoint(env)
236
- path = path(env)
237
- group_name, xfer_id, action =
238
- path.match(%r{\A/groups/(.*)/transfers/(.*)/actions/(.*)\z}) && [$1, $2, $3]
239
- verb = verb(env)
240
- if verb == 'POST'
241
- body = body(env)
242
- args = JSON.parse(body) unless body.empty?
243
- take_transfer_action(action, args, group_name, xfer_id)
244
- else
245
- [405, headers, []]
246
- end
247
- end
248
-
249
- def transfers_endpoint(env)
250
- path = path(env)
251
- group_name, xfer_id = path.match(%r{\A/groups/(.*)/transfers(?:/(.*))?\z}) && [$1, $2]
252
- verb = verb(env)
253
- if verb == 'POST'
254
- body = body(env)
255
- xfer = JSON.parse(body)
256
- unless xfer_id.nil?
257
- [405, headers, []]
258
- end
259
- add_transfer(group_name, xfer)
260
- elsif verb == 'DELETE'
261
- unless group_name && xfer_id
262
- return [404, headers, []]
263
- end
264
- delete_transfer(group_name, xfer_id)
265
- elsif verb == 'GET'
266
- if xfer_id.nil?
267
- list_transfers(group_name)
268
- else
269
- get_transfer(group_name, xfer_id, params(env)['verbose'] == 'true')
270
- end
271
- else
272
- [405, headers, []]
273
- end
274
- end
275
-
276
- def groups_endpoint(env)
277
- path = path(env)
278
- verb = verb(env)
279
-
280
- group_name = path.match(%r{\A/groups/(.*)\z}) && $1
3
+ require "securerandom"
4
+ require "spec_helper"
281
5
 
282
- if verb == 'GET'
283
- if group_name.nil?
284
- list_groups
285
- else
286
- get_group(group_name)
287
- end
288
- elsif verb == 'POST'
289
- body = body(env)
290
- data = JSON.parse(body)
291
- add_group(data["name"], data["log_input_url"])
292
- elsif verb == 'DELETE'
293
- name = path.match(%r{\A/groups/(.*)\z}) && $1
294
- unless name
295
- return [404, headers, []]
296
- end
297
- delete_group(name)
298
- else
299
- [405, headers, []]
300
- end
301
- end
302
-
303
- def schedules_endpoint(env)
304
- path = path(env)
305
- group_name, sched_id = path.match(%r{\A/groups/(.*)/schedules(?:/(.*))?\z}) && [$1, $2]
306
- verb = verb(env)
307
- if %w(POST PUT).include? verb
308
- body = body(env)
309
- sched = JSON.parse(body)
310
- unless sched_id.nil?
311
- [405, headers, []]
312
- end
313
- add_schedule(group_name, sched)
314
- elsif verb == 'DELETE'
315
- unless group_name && sched_id
316
- return [404, headers, []]
317
- end
318
- delete_schedule(group_name, sched_id)
319
- elsif verb == 'GET'
320
- if sched_id.nil?
321
- list_schedules(group_name)
322
- else
323
- get_schedule(group_name, sched_id)
324
- end
325
- else
326
- [405, headers, []]
327
- end
328
- end
329
-
330
- private
331
- def verify_auth(env)
332
- auth = Rack::Auth::Basic::Request.new(env)
333
- if auth.provided? && auth.basic?
334
- user, password = auth.credentials
335
- @users.any? { |u| u.name == user && u.password == password }
336
- end
337
- end
338
-
339
- def path(rack_env)
340
- rack_env['PATH_INFO']
341
- end
342
-
343
- def verb(rack_env)
344
- rack_env['REQUEST_METHOD']
345
- end
346
-
347
- def params(rack_env)
348
- Rack::Utils.parse_nested_query rack_env['QUERY_STRING']
349
- end
350
-
351
- def body(rack_env)
352
- raw_body = rack_env["rack.input"].read
353
- rack_env["rack.input"].rewind
354
- raw_body
355
- end
356
- end
357
-
358
- describe Client do
359
- let(:username) { 'reginald' }
360
- let(:password) { 'hunter2' }
361
- let(:client) { Client.new(username, password) }
6
+ module Xfrtuc
7
+ RSpec.describe Client do
8
+ let(:username) { "reginald" }
9
+ let(:password) { "hunter2" }
10
+ let(:client) { described_class.new(username, password) }
362
11
 
363
12
  describe "#group" do
364
13
  context "with an argument" do
365
- let(:group_name) { 'foo' }
14
+ let(:group_name) { "foo" }
366
15
 
367
16
  it "returns a new client rooted at that group's base URL" do
368
17
  group_client = client.group(group_name)
369
- expect(group_client).to be_instance_of(Client)
18
+ expect(group_client).to be_instance_of(described_class)
370
19
  expect(group_client.base_url).to eq(client.base_url +
371
20
  "/groups/#{CGI.escape(group_name)}")
372
21
  end
@@ -392,55 +41,52 @@ module Xfrtuc
392
41
  end
393
42
  end
394
43
 
395
- describe "api interactions" do
396
- let(:username) { 'vivian' }
397
- let(:password) { 'hunter2' }
398
- let(:user) { User.new(username, password) }
399
- let(:fakesferatu) { FakeTransferatu.new(user) }
400
- let(:host) { 'transferatu.example.com' }
401
- let(:client) { Client.new(username, password, "https://#{host}") }
402
-
403
- before do
404
- ShamRack.at(host, 443).mount(fakesferatu)
405
- end
406
-
407
- after do
408
- ShamRack.unmount_all
409
- end
44
+ RSpec.describe "api interactions" do
45
+ let(:username) { "vivian" }
46
+ let(:password) { "hunter2" }
47
+ let(:host) { "transferatu.example.com" }
48
+ let(:base_url) { "https://#{host}" }
49
+ let(:client) { Client.new(username, password, base_url) }
410
50
 
411
51
  describe Group do
412
- let(:group_name) { "edna" }
52
+ let(:group_name) { "edna" }
413
53
  let(:log_input_url) { "https://token:t.foo@logplex.example.com/logs" }
414
54
 
415
55
  describe "#create" do
416
56
  it "creates a new group" do
417
- client.group.create("edna", log_input_url)
418
- group = fakesferatu.groups.last
419
- expect(group[:name]).to eq(group_name)
420
- expect(group[:log_input_url]).to eq(log_input_url)
57
+ WebMock.stub_request(:post, "#{base_url}/groups")
58
+ .with(basic_auth: [username, password],
59
+ body: { name: group_name, log_input_url: log_input_url },)
60
+ .to_return_json(status: 201,
61
+ body: { name: group_name, log_input_url: log_input_url },)
62
+
63
+ result = client.group.create(group_name, log_input_url)
64
+ expect(result["name"]).to eq(group_name)
65
+ expect(result["log_input_url"]).to eq(log_input_url)
421
66
  end
422
67
  end
423
68
 
424
69
  describe "#list" do
425
- before do
426
- fakesferatu.add_group('g1')
427
- fakesferatu.add_group('g2')
428
- end
429
-
430
70
  it "lists existing groups" do
71
+ groups = [{ name: "g1" }, { name: "g2" }]
72
+ WebMock.stub_request(:get, "#{base_url}/groups")
73
+ .with(basic_auth: [username, password])
74
+ .to_return_json(status: 200, body: groups)
75
+
431
76
  result = client.group.list
432
77
  expect(result.count).to eq(2)
433
- expect(result.first["name"]).to eq('g1')
434
- expect(result.last["name"]).to eq('g2')
78
+ expect(result.first["name"]).to eq("g1")
79
+ expect(result.last["name"]).to eq("g2")
435
80
  end
436
81
  end
437
82
 
438
83
  describe "#info" do
439
- before do
440
- fakesferatu.add_group(group_name, log_input_url)
441
- end
442
-
443
84
  it "returns details for the given group" do
85
+ group = { name: group_name, log_input_url: log_input_url }
86
+ WebMock.stub_request(:get, "#{base_url}/groups/#{group_name}")
87
+ .with(basic_auth: [username, password])
88
+ .to_return_json(status: 200, body: group)
89
+
444
90
  info = client.group.info(group_name)
445
91
  expect(info["name"]).to eq(group_name)
446
92
  expect(info["log_input_url"]).to eq(log_input_url)
@@ -448,63 +94,82 @@ module Xfrtuc
448
94
  end
449
95
 
450
96
  describe "#delete" do
451
- before do
452
- fakesferatu.add_group(group_name, log_input_url)
453
- end
454
-
455
97
  it "deletes the given group" do
456
- client.group.delete(group_name)
457
- deleted_group = fakesferatu.groups.find { |g| g[:name] == group_name }
458
- expect(deleted_group[:deleted]).to be true
98
+ WebMock.stub_request(:delete, "#{base_url}/groups/#{group_name}")
99
+ .with(basic_auth: [username, password])
100
+ .to_return_json(status: 200, body: { name: group_name, deleted: true })
101
+
102
+ result = client.group.delete(group_name)
103
+ expect(result["name"]).to eq(group_name)
459
104
  end
460
105
  end
461
106
  end
462
107
 
463
108
  describe Transfer do
464
- let(:g) { "edna" }
465
- let(:xfer_data) { { from_url: 'postgres:///test1',
466
- from_name: 'earl', from_type: 'pg_dump',
467
- to_url: 'postgres:///test2',
468
- to_name: 'mildred', to_type: 'pg_restore' } }
469
-
470
- before do
471
- fakesferatu.add_group(g)
472
- end
109
+ let(:g) { "edna" }
110
+ let(:xfer_data) {
111
+ { from_url: "postgres:///test1",
112
+ from_name: "earl", from_type: "pg_dump",
113
+ to_url: "postgres:///test2",
114
+ to_name: "mildred", to_type: "pg_restore", }
115
+ }
116
+ let(:xfer_id) { SecureRandom.uuid }
473
117
 
474
118
  describe "#create" do
475
119
  it "creates a new transfer" do
476
- client.group(g).transfer.create(xfer_data)
477
- xfer = fakesferatu.last_transfer(g)
478
- xfer_data.each do |k,v|
479
- expect(xfer[k]).to eq(v)
120
+ response = xfer_data.merge(uuid: xfer_id)
121
+ WebMock.stub_request(:post, "#{base_url}/groups/#{g}/transfers")
122
+ .with(basic_auth: [username, password])
123
+ .to_return_json(status: 201, body: response)
124
+
125
+ result = client.group(g).transfer.create(xfer_data)
126
+ xfer_data.each do |k, v|
127
+ expect(result[k.to_s]).to eq(v)
480
128
  end
481
129
  end
482
130
 
483
131
  it "accepts an optional log_input_url" do
484
132
  log_url = "https://example.com/logs"
485
- client.group(g).transfer.create(xfer_data.merge(log_input_url: log_url))
486
- xfer = fakesferatu.last_transfer(g)
487
- expect(xfer[:log_input_url]).to eq(log_url)
133
+ response = xfer_data.merge(uuid: xfer_id, log_input_url: log_url)
134
+ WebMock.stub_request(:post, "#{base_url}/groups/#{g}/transfers")
135
+ .with(basic_auth: [username, password],
136
+ body: hash_including("log_input_url" => log_url),)
137
+ .to_return_json(status: 201, body: response)
138
+
139
+ result = client.group(g).transfer.create(xfer_data.merge(log_input_url: log_url))
140
+ expect(result["log_input_url"]).to eq(log_url)
488
141
  end
489
142
 
490
143
  it "accepts an optional num_keep" do
491
144
  num_keep = 3
492
- client.group(g).transfer.create(xfer_data.merge(num_keep: num_keep))
493
- xfer = fakesferatu.last_transfer(g)
494
- expect(xfer[:num_keep]).to eq(num_keep)
145
+ response = xfer_data.merge(uuid: xfer_id, num_keep: num_keep)
146
+ WebMock.stub_request(:post, "#{base_url}/groups/#{g}/transfers")
147
+ .with(basic_auth: [username, password],
148
+ body: hash_including("num_keep" => num_keep),)
149
+ .to_return_json(status: 201, body: response)
150
+
151
+ result = client.group(g).transfer.create(xfer_data.merge(num_keep: num_keep))
152
+ expect(result["num_keep"]).to eq(num_keep)
495
153
  end
496
- end
497
154
 
498
- describe "#list" do
499
- before do
500
- 2.times { fakesferatu.add_transfer(g, Hash[xfer_data.map { |k, v| [k.to_s, v] }]) }
155
+ it "raises ArgumentError for unsupported options" do
156
+ expect {
157
+ client.group(g).transfer.create(xfer_data.merge(bogus: "value"))
158
+ }.to raise_error(ArgumentError, /Unsupported option/)
501
159
  end
160
+ end
502
161
 
162
+ describe "#list" do
503
163
  it "lists existing transfers" do
504
- xfers = client.group(g).transfer.list
505
- expect(xfers.count).to eq(2)
506
- xfers.each do |xfer|
507
- xfer_data.each do |k,v|
164
+ xfers = Array.new(2) { xfer_data.merge(uuid: SecureRandom.uuid) }
165
+ WebMock.stub_request(:get, "#{base_url}/groups/#{g}/transfers")
166
+ .with(basic_auth: [username, password])
167
+ .to_return_json(status: 200, body: xfers)
168
+
169
+ result = client.group(g).transfer.list
170
+ expect(result.count).to eq(2)
171
+ result.each do |xfer|
172
+ xfer_data.each do |k, v|
508
173
  expect(xfer[k.to_s]).to eq(v)
509
174
  end
510
175
  end
@@ -512,118 +177,283 @@ module Xfrtuc
512
177
  end
513
178
 
514
179
  describe "#info" do
515
- before do
516
- fakesferatu.add_transfer(g, Hash[xfer_data.map { |k, v| [k.to_s, v] }])
517
- end
518
-
519
180
  it "gets info for an existing transfer" do
520
- id = fakesferatu.last_transfer(g)[:uuid]
521
- xfer = client.group(g).transfer.info(id)
522
- xfer_data.each do |k,v|
181
+ response = xfer_data.merge(uuid: xfer_id)
182
+ WebMock.stub_request(:get, "#{base_url}/groups/#{g}/transfers/#{xfer_id}")
183
+ .with(basic_auth: [username, password],
184
+ query: { "verbose" => "false" },)
185
+ .to_return_json(status: 200, body: response)
186
+
187
+ xfer = client.group(g).transfer.info(xfer_id)
188
+ xfer_data.each do |k, v|
523
189
  expect(xfer[k.to_s]).to eq(v)
524
190
  end
525
191
  expect(xfer["logs"]).to be_nil
526
192
  end
527
193
 
528
194
  it "includes logs when verbose mode is requested" do
529
- id = fakesferatu.last_transfer(g)[:uuid]
530
- xfer = client.group(g).transfer.info(id, verbose: true)
531
- xfer_data.each do |k,v|
195
+ response = xfer_data.merge(uuid: xfer_id, logs: [])
196
+ WebMock.stub_request(:get, "#{base_url}/groups/#{g}/transfers/#{xfer_id}")
197
+ .with(basic_auth: [username, password],
198
+ query: { "verbose" => "true" },)
199
+ .to_return_json(status: 200, body: response)
200
+
201
+ xfer = client.group(g).transfer.info(xfer_id, verbose: true)
202
+ xfer_data.each do |k, v|
532
203
  expect(xfer[k.to_s]).to eq(v)
533
204
  end
534
205
  expect(xfer["logs"]).not_to be_nil
535
206
  end
536
- end
537
207
 
538
- describe "#delete" do
539
- before do
540
- fakesferatu.add_transfer(g, Hash[xfer_data.map { |k, v| [k.to_s, v] }])
208
+ it "raises ArgumentError for unsupported options" do
209
+ expect {
210
+ client.group(g).transfer.info(xfer_id, bogus: "value")
211
+ }.to raise_error(ArgumentError, /Unsupported option/)
541
212
  end
213
+ end
542
214
 
215
+ describe "#delete" do
543
216
  it "deletes the given transfer" do
544
- id = fakesferatu.last_transfer(g)[:uuid]
545
- client.group(g).transfer.delete(id)
546
- expect(fakesferatu.last_transfer(g)).to be_nil
217
+ response = xfer_data.merge(uuid: xfer_id)
218
+ WebMock.stub_request(:delete, "#{base_url}/groups/#{g}/transfers/#{xfer_id}")
219
+ .with(basic_auth: [username, password])
220
+ .to_return_json(status: 200, body: response)
221
+
222
+ result = client.group(g).transfer.delete(xfer_id)
223
+ expect(result["uuid"]).to eq(xfer_id)
547
224
  end
548
225
  end
549
226
 
550
227
  describe "#cancel" do
551
- before do
552
- fakesferatu.add_transfer(g, Hash[xfer_data.map { |k, v| [k.to_s, v] }])
553
- end
554
-
555
228
  it "cancels the given transfer" do
556
- id = fakesferatu.last_transfer(g)[:uuid]
557
- before = Time.now
558
- cancel_data = client.group(g).transfer.cancel(id)
229
+ now = Time.now
230
+ WebMock.stub_request(:post, "#{base_url}/groups/#{g}/transfers/#{xfer_id}/actions/cancel")
231
+ .with(basic_auth: [username, password])
232
+ .to_return_json(status: 201, body: { canceled_at: now })
233
+
234
+ cancel_data = client.group(g).transfer.cancel(xfer_id)
559
235
  canceled_at = Time.parse(cancel_data["canceled_at"])
560
- expect(canceled_at).to be_within(60).of(before)
236
+ expect(canceled_at).to be_within(60).of(now)
561
237
  end
562
238
  end
563
239
 
564
240
  describe "#public_url" do
565
- before do
566
- fakesferatu.add_transfer(g, Hash[xfer_data.map { |k, v| [k.to_s, v] }])
567
- end
568
-
569
241
  it "provides a public url for the given transfer" do
570
- id = fakesferatu.last_transfer(g)[:uuid]
571
- url_data = client.group(g).transfer.public_url(id)
242
+ url = "https://example.com/backup/#{xfer_id}"
243
+ expires_at = Time.now + (10 * 60)
244
+ WebMock.stub_request(:post, "#{base_url}/groups/#{g}/transfers/#{xfer_id}/actions/public-url")
245
+ .with(basic_auth: [username, password])
246
+ .to_return_json(status: 201, body: { url: url, expires_at: expires_at })
247
+
248
+ url_data = client.group(g).transfer.public_url(xfer_id)
572
249
  expect { URI.parse(url_data["url"]) }.not_to raise_error
573
250
  end
574
251
 
575
252
  it "supports an optional ttl parameter" do
576
- id = fakesferatu.last_transfer(g)[:uuid]
577
- before = Time.now
578
- url_data = client.group(g).transfer.public_url(id, ttl: 5 * 60)
579
- expires_at = Time.parse(url_data["expires_at"])
580
- expect(expires_at).to be_within(60).of(before + (5 * 60))
253
+ url = "https://example.com/backup/#{xfer_id}"
254
+ now = Time.now
255
+ expires_at = now + (5 * 60)
256
+ WebMock.stub_request(:post, "#{base_url}/groups/#{g}/transfers/#{xfer_id}/actions/public-url")
257
+ .with(basic_auth: [username, password],
258
+ body: { ttl: 300 }.to_json,)
259
+ .to_return_json(status: 201, body: { url: url, expires_at: expires_at })
260
+
261
+ url_data = client.group(g).transfer.public_url(xfer_id, ttl: 5 * 60)
262
+ expires = Time.parse(url_data["expires_at"])
263
+ expect(expires).to be_within(60).of(now + (5 * 60))
581
264
  end
582
265
  end
583
266
  end
584
267
 
585
- describe Schedule do
586
- let(:g) { "edna" }
587
- let(:sched_data) { { name: 'my schedule',
588
- callback_url: 'https://example.com/callback/foo',
589
- hour: 13,
590
- days: Date::DAYNAMES,
591
- timezone: 'UTC' } }
268
+ describe "error handling" do
269
+ it "does not raise for 2xx responses" do
270
+ [200, 201, 204].each do |status|
271
+ WebMock.stub_request(:get, "#{base_url}/groups")
272
+ .with(basic_auth: [username, password])
273
+ .to_return_json(status: status, body: [])
274
+
275
+ expect { client.group.list }.not_to raise_error
276
+ WebMock.reset!
277
+ end
278
+ end
279
+
280
+ it "raises BadRequest for 400" do
281
+ WebMock.stub_request(:get, "#{base_url}/groups")
282
+ .with(basic_auth: [username, password])
283
+ .to_return(status: 400, body: '{"error":"bad request"}')
284
+
285
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::BadRequest, /got 400/)
286
+ end
287
+
288
+ it "raises NotFound for 404" do
289
+ WebMock.stub_request(:get, "#{base_url}/groups")
290
+ .with(basic_auth: [username, password])
291
+ .to_return(status: 404, body: '{"error":"not found"}')
292
+
293
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::NotFound, /got 404/)
294
+ end
295
+
296
+ it "raises Conflict for 409" do
297
+ WebMock.stub_request(:get, "#{base_url}/groups")
298
+ .with(basic_auth: [username, password])
299
+ .to_return(status: 409, body: '{"error":"conflict"}')
300
+
301
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::Conflict, /got 409/)
302
+ end
303
+
304
+ it "raises Gone for 410" do
305
+ WebMock.stub_request(:get, "#{base_url}/groups")
306
+ .with(basic_auth: [username, password])
307
+ .to_return(status: 410, body: '{"error":"gone"}')
308
+
309
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::Gone, /got 410/)
310
+ end
311
+
312
+ it "raises ClientError for other 4xx responses" do
313
+ WebMock.stub_request(:get, "#{base_url}/groups")
314
+ .with(basic_auth: [username, password])
315
+ .to_return(status: 422, body: '{"error":"unprocessable"}')
316
+
317
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::ClientError, /got 422/)
318
+ end
319
+
320
+ it "raises ServerError for 500" do
321
+ WebMock.stub_request(:get, "#{base_url}/groups")
322
+ .with(basic_auth: [username, password])
323
+ .to_return(status: 500, body: '{"error":"internal"}')
324
+
325
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::ServerError, /got 500/)
326
+ end
327
+
328
+ it "raises ServiceUnavailable for 503" do
329
+ WebMock.stub_request(:get, "#{base_url}/groups")
330
+ .with(basic_auth: [username, password])
331
+ .to_return(status: 503, body: '{"error":"unavailable"}')
332
+
333
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::ServiceUnavailable, /got 503/)
334
+ end
335
+
336
+ it "ServiceUnavailable is rescuable as ServerError" do
337
+ WebMock.stub_request(:get, "#{base_url}/groups")
338
+ .with(basic_auth: [username, password])
339
+ .to_return(status: 503, body: '{"error":"unavailable"}')
340
+
341
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::ServerError)
342
+ end
343
+
344
+ it "raises ServerError for other 5xx responses" do
345
+ WebMock.stub_request(:get, "#{base_url}/groups")
346
+ .with(basic_auth: [username, password])
347
+ .to_return(status: 502, body: '{"error":"bad gateway"}')
348
+
349
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::ServerError, /got 502/)
350
+ end
351
+
352
+ it "all HTTP errors are rescuable as Xfrtuc::HTTP::Error" do
353
+ WebMock.stub_request(:get, "#{base_url}/groups")
354
+ .with(basic_auth: [username, password])
355
+ .to_return(status: 500, body: '{"error":"internal"}')
356
+
357
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::Error)
358
+ end
359
+
360
+ it "raises ConnectionResetError on ECONNRESET" do
361
+ WebMock.stub_request(:get, "#{base_url}/groups")
362
+ .with(basic_auth: [username, password])
363
+ .to_raise(Errno::ECONNRESET)
364
+
365
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::ConnectionResetError)
366
+ end
367
+
368
+ it "raises SocketError on connection refused" do
369
+ WebMock.stub_request(:get, "#{base_url}/groups")
370
+ .with(basic_auth: [username, password])
371
+ .to_raise(Errno::ECONNREFUSED)
592
372
 
593
- before do
594
- fakesferatu.add_group(g)
373
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::SocketError)
595
374
  end
596
375
 
376
+ it "raises SocketError on DNS failure" do
377
+ WebMock.stub_request(:get, "#{base_url}/groups")
378
+ .with(basic_auth: [username, password])
379
+ .to_raise(SocketError.new("getaddrinfo: Name or service not known"))
380
+
381
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::SocketError)
382
+ end
383
+
384
+ it "raises SocketError on timeout" do
385
+ WebMock.stub_request(:get, "#{base_url}/groups")
386
+ .with(basic_auth: [username, password])
387
+ .to_timeout
388
+
389
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::SocketError)
390
+ end
391
+
392
+ it "network errors are rescuable as Xfrtuc::HTTP::Error" do
393
+ WebMock.stub_request(:get, "#{base_url}/groups")
394
+ .with(basic_auth: [username, password])
395
+ .to_raise(Errno::ECONNREFUSED)
396
+
397
+ expect { client.group.list }.to raise_error(Xfrtuc::HTTP::Error)
398
+ end
399
+ end
400
+
401
+ describe Schedule do
402
+ let(:g) { "edna" }
403
+ let(:sched_data) {
404
+ { name: "my schedule",
405
+ callback_url: "https://example.com/callback/foo",
406
+ hour: 13,
407
+ days: Date::DAYNAMES,
408
+ timezone: "UTC", }
409
+ }
410
+ let(:sched_id) { SecureRandom.uuid }
411
+
597
412
  describe "#create" do
598
413
  it "creates a new schedule" do
599
- client.group(g).schedule.create(sched_data)
600
- sched = fakesferatu.last_schedule(g)
601
- sched_data.each do |k,v|
602
- expect(sched[k]).to eq(v)
414
+ response = sched_data.merge(uuid: sched_id)
415
+ WebMock.stub_request(:put, "#{base_url}/groups/#{g}/schedules/#{CGI.escape(sched_data[:name])}")
416
+ .with(basic_auth: [username, password])
417
+ .to_return_json(status: 201, body: response)
418
+
419
+ result = client.group(g).schedule.create(sched_data)
420
+ sched_data.each do |k, v|
421
+ expect(result[k.to_s]).to eq(v)
603
422
  end
604
423
  end
605
424
 
606
425
  it "accepts an optional retain_weeks and retain_months" do
607
426
  retain_weeks = 7
608
427
  retain_months = 8
609
- client.group(g).schedule.create(sched_data.merge(retain_weeks: retain_weeks,
610
- retain_months: retain_months))
611
- sched = fakesferatu.last_schedule(g)
612
- expect(sched[:retain_weeks]).to eq(retain_weeks)
613
- expect(sched[:retain_months]).to eq(retain_months)
428
+ response = sched_data.merge(uuid: sched_id, retain_weeks: retain_weeks, retain_months: retain_months)
429
+ WebMock.stub_request(:put, "#{base_url}/groups/#{g}/schedules/#{CGI.escape(sched_data[:name])}")
430
+ .with(basic_auth: [username, password],
431
+ body: hash_including("retain_weeks" => retain_weeks, "retain_months" => retain_months),)
432
+ .to_return_json(status: 201, body: response)
433
+
434
+ result = client.group(g).schedule.create(sched_data.merge(retain_weeks: retain_weeks, retain_months: retain_months))
435
+ expect(result["retain_weeks"]).to eq(retain_weeks)
436
+ expect(result["retain_months"]).to eq(retain_months)
614
437
  end
615
- end
616
438
 
617
- describe "#list" do
618
- before do
619
- 2.times { fakesferatu.add_schedule(g, Hash[sched_data.map { |k, v| [k.to_s, v] }]) }
439
+ it "raises ArgumentError for unsupported options" do
440
+ expect {
441
+ client.group(g).schedule.create(sched_data.merge(bogus: "value"))
442
+ }.to raise_error(ArgumentError, /Unsupported option/)
620
443
  end
444
+ end
621
445
 
446
+ describe "#list" do
622
447
  it "lists existing schedules" do
623
- scheds = client.group(g).schedule.list
624
- expect(scheds.count).to eq(2)
625
- scheds.each do |sched|
626
- sched_data.each do |k,v|
448
+ scheds = Array.new(2) { sched_data.merge(uuid: SecureRandom.uuid) }
449
+ WebMock.stub_request(:get, "#{base_url}/groups/#{g}/schedules")
450
+ .with(basic_auth: [username, password])
451
+ .to_return_json(status: 200, body: scheds)
452
+
453
+ result = client.group(g).schedule.list
454
+ expect(result.count).to eq(2)
455
+ result.each do |sched|
456
+ sched_data.each do |k, v|
627
457
  expect(sched[k.to_s]).to eq(v)
628
458
  end
629
459
  end
@@ -631,28 +461,28 @@ module Xfrtuc
631
461
  end
632
462
 
633
463
  describe "#info" do
634
- before do
635
- fakesferatu.add_schedule(g, Hash[sched_data.map { |k, v| [k.to_s, v] }])
636
- end
637
-
638
464
  it "gets info for an existing schedule" do
639
- id = fakesferatu.last_schedule(g)[:uuid]
640
- sched = client.group(g).schedule.info(id)
641
- sched_data.each do |k,v|
465
+ response = sched_data.merge(uuid: sched_id)
466
+ WebMock.stub_request(:get, "#{base_url}/groups/#{g}/schedules/#{sched_id}")
467
+ .with(basic_auth: [username, password])
468
+ .to_return_json(status: 200, body: response)
469
+
470
+ sched = client.group(g).schedule.info(sched_id)
471
+ sched_data.each do |k, v|
642
472
  expect(sched[k.to_s]).to eq(v)
643
473
  end
644
474
  end
645
475
  end
646
476
 
647
477
  describe "#delete" do
648
- before do
649
- fakesferatu.add_schedule(g, Hash[sched_data.map { |k, v| [k.to_s, v] }])
650
- end
651
-
652
478
  it "deletes the given schedule" do
653
- id = fakesferatu.last_schedule(g)[:uuid]
654
- client.group(g).schedule.delete(id)
655
- expect(fakesferatu.last_schedule(g)).to be_nil
479
+ response = sched_data.merge(uuid: sched_id)
480
+ WebMock.stub_request(:delete, "#{base_url}/groups/#{g}/schedules/#{sched_id}")
481
+ .with(basic_auth: [username, password])
482
+ .to_return_json(status: 200, body: response)
483
+
484
+ result = client.group(g).schedule.delete(sched_id)
485
+ expect(result["uuid"]).to eq(sched_id)
656
486
  end
657
487
  end
658
488
  end