datadog_backup 3.0.0.alpha.2 → 3.1.1
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 +4 -4
- data/.github/workflows/rspec_and_release.yml +0 -2
- data/.gitignore +3 -1
- data/.rubocop.yml +36 -77
- data/CHANGELOG.md +39 -0
- data/README.md +2 -2
- data/bin/datadog_backup +7 -5
- data/datadog_backup.gemspec +3 -4
- data/lib/datadog_backup/cli.rb +47 -51
- data/lib/datadog_backup/dashboards.rb +21 -16
- data/lib/datadog_backup/deprecations.rb +4 -5
- data/lib/datadog_backup/local_filesystem.rb +4 -5
- data/lib/datadog_backup/monitors.rb +16 -13
- data/lib/datadog_backup/options.rb +1 -0
- data/lib/datadog_backup/resources.rb +176 -0
- data/lib/datadog_backup/synthetics.rb +68 -0
- data/lib/datadog_backup/thread_pool.rb +1 -0
- data/lib/datadog_backup/version.rb +1 -1
- data/lib/datadog_backup.rb +3 -3
- data/release.config.js +4 -0
- data/spec/datadog_backup/cli_spec.rb +67 -46
- data/spec/datadog_backup/core_spec.rb +72 -51
- data/spec/datadog_backup/dashboards_spec.rb +17 -32
- data/spec/datadog_backup/deprecations_spec.rb +7 -9
- data/spec/datadog_backup/local_filesystem_spec.rb +42 -42
- data/spec/datadog_backup/monitors_spec.rb +13 -29
- data/spec/datadog_backup/synthetics_spec.rb +258 -0
- data/spec/datadog_backup_bin_spec.rb +17 -15
- data/spec/spec_helper.rb +69 -5
- metadata +13 -21
- data/Gemfile.lock +0 -118
- data/lib/datadog_backup/core.rb +0 -148
- data/spec/datadog_backup_spec.rb +0 -6
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'diffy'
|
4
|
+
require 'deepsort'
|
5
|
+
require 'faraday'
|
6
|
+
require 'faraday/retry'
|
7
|
+
|
8
|
+
module DatadogBackup
|
9
|
+
# The default options for backing up and restores.
|
10
|
+
# This base class is meant to be extended by specific resources, such as Dashboards, Monitors, and so on.
|
11
|
+
class Resources
|
12
|
+
include ::DatadogBackup::LocalFilesystem
|
13
|
+
include ::DatadogBackup::Options
|
14
|
+
|
15
|
+
RETRY_OPTIONS = {
|
16
|
+
max: 5,
|
17
|
+
interval: 0.05,
|
18
|
+
interval_randomness: 0.5,
|
19
|
+
backoff_factor: 2
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
def backup
|
23
|
+
raise 'subclass is expected to implement #backup'
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the diffy diff.
|
27
|
+
# Optionally, supply an array of keys to remove from comparison
|
28
|
+
def diff(id)
|
29
|
+
current = except(get_by_id(id)).deep_sort.to_yaml
|
30
|
+
filesystem = except(load_from_file_by_id(id)).deep_sort.to_yaml
|
31
|
+
result = ::Diffy::Diff.new(current, filesystem, include_plus_and_minus_in_html: true).to_s(diff_format)
|
32
|
+
LOGGER.debug("Compared ID #{id} and found filesystem: #{filesystem} <=> current: #{current} == result: #{result}")
|
33
|
+
result.chomp
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a hash with banlist elements removed
|
37
|
+
def except(hash)
|
38
|
+
hash.tap do # tap returns self
|
39
|
+
@banlist.each do |key|
|
40
|
+
hash.delete(key) # delete returns the value at the deleted key, hence the tap wrapper
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fetch the specified resource from Datadog
|
46
|
+
def get(id)
|
47
|
+
params = {}
|
48
|
+
headers = {}
|
49
|
+
response = api_service.get("/api/#{api_version}/#{api_resource_name}/#{id}", params, headers)
|
50
|
+
body_with_2xx(response)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a list of all resources in Datadog
|
54
|
+
# Do not use directly, but use the child classes' #all method instead
|
55
|
+
def get_all
|
56
|
+
return @get_all if @get_all
|
57
|
+
|
58
|
+
params = {}
|
59
|
+
headers = {}
|
60
|
+
response = api_service.get("/api/#{api_version}/#{api_resource_name}", params, headers)
|
61
|
+
@get_all = body_with_2xx(response)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Download the resource from Datadog and write it to a file
|
65
|
+
def get_and_write_file(id)
|
66
|
+
body = get_by_id(id)
|
67
|
+
write_file(dump(body), filename(id))
|
68
|
+
body
|
69
|
+
end
|
70
|
+
|
71
|
+
# Fetch the specified resource from Datadog and remove the banlist elements
|
72
|
+
def get_by_id(id)
|
73
|
+
except(get(id))
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize(options)
|
77
|
+
@options = options
|
78
|
+
@banlist = []
|
79
|
+
::FileUtils.mkdir_p(mydir)
|
80
|
+
end
|
81
|
+
|
82
|
+
def myclass
|
83
|
+
self.class.to_s.split(':').last.downcase
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create a new resource in Datadog
|
87
|
+
def create(body)
|
88
|
+
headers = {}
|
89
|
+
response = api_service.post("/api/#{api_version}/#{api_resource_name}", body, headers)
|
90
|
+
body = body_with_2xx(response)
|
91
|
+
LOGGER.warn "Successfully created #{body.fetch(id_keyname)} in datadog."
|
92
|
+
LOGGER.info 'Invalidating cache'
|
93
|
+
@get_all = nil
|
94
|
+
body
|
95
|
+
end
|
96
|
+
|
97
|
+
# Update an existing resource in Datadog
|
98
|
+
def update(id, body)
|
99
|
+
headers = {}
|
100
|
+
response = api_service.put("/api/#{api_version}/#{api_resource_name}/#{id}", body, headers)
|
101
|
+
body = body_with_2xx(response)
|
102
|
+
LOGGER.warn "Successfully restored #{id} to datadog."
|
103
|
+
LOGGER.info 'Invalidating cache'
|
104
|
+
@get_all = nil
|
105
|
+
body
|
106
|
+
end
|
107
|
+
|
108
|
+
# If the resource exists in Datadog, update it. Otherwise, create it.
|
109
|
+
def restore(id)
|
110
|
+
body = load_from_file_by_id(id)
|
111
|
+
begin
|
112
|
+
update(id, body)
|
113
|
+
rescue RuntimeError => e
|
114
|
+
raise e.message unless e.message.include?('update failed with error 404')
|
115
|
+
|
116
|
+
create_newly(id, body)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return the Faraday body from a response with a 2xx status code, otherwise raise an error
|
121
|
+
def body_with_2xx(response)
|
122
|
+
unless response.status.to_s =~ /^2/
|
123
|
+
raise "#{caller_locations(1,
|
124
|
+
1)[0].label} failed with error #{response.status}"
|
125
|
+
end
|
126
|
+
|
127
|
+
response.body
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def api_url
|
133
|
+
ENV.fetch('DD_SITE_URL', 'https://api.datadoghq.com/')
|
134
|
+
end
|
135
|
+
|
136
|
+
def api_version
|
137
|
+
raise 'subclass is expected to implement #api_version'
|
138
|
+
end
|
139
|
+
|
140
|
+
def api_resource_name
|
141
|
+
raise 'subclass is expected to implement #api_resource_name'
|
142
|
+
end
|
143
|
+
|
144
|
+
# Some resources have a different key for the id.
|
145
|
+
def id_keyname
|
146
|
+
'id'
|
147
|
+
end
|
148
|
+
|
149
|
+
def api_service
|
150
|
+
@api_service ||= Faraday.new(
|
151
|
+
url: api_url,
|
152
|
+
headers: {
|
153
|
+
'DD-API-KEY' => ENV.fetch('DD_API_KEY'),
|
154
|
+
'DD-APPLICATION-KEY' => ENV.fetch('DD_APP_KEY')
|
155
|
+
}
|
156
|
+
) do |faraday|
|
157
|
+
faraday.request :json
|
158
|
+
faraday.request :retry, RETRY_OPTIONS
|
159
|
+
faraday.response(:logger, LOGGER, { headers: true, bodies: LOGGER.debug?, log_level: :debug }) do |logger|
|
160
|
+
logger.filter(/(DD-API-KEY:)([^&]+)/, '\1[REDACTED]')
|
161
|
+
logger.filter(/(DD-APPLICATION-KEY:)([^&]+)/, '\1[REDACTED]')
|
162
|
+
end
|
163
|
+
faraday.response :raise_error
|
164
|
+
faraday.response :json
|
165
|
+
faraday.adapter Faraday.default_adapter
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Create a new resource in Datadog, then move the old file to the new resource's ID
|
170
|
+
def create_newly(file_id, body)
|
171
|
+
new_id = create(body).fetch(id_keyname)
|
172
|
+
FileUtils.rm(find_file_by_id(file_id))
|
173
|
+
get_and_write_file(new_id)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DatadogBackup
|
4
|
+
# Synthetic specific overrides for backup and restore.
|
5
|
+
class Synthetics < Resources
|
6
|
+
def all
|
7
|
+
get_all.fetch('tests')
|
8
|
+
end
|
9
|
+
|
10
|
+
def backup
|
11
|
+
all.map do |synthetic|
|
12
|
+
id = synthetic[id_keyname]
|
13
|
+
get_and_write_file(id)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_by_id(id)
|
18
|
+
synthetic = all.select { |s| s[id_keyname].to_s == id.to_s }.first
|
19
|
+
synthetic.nil? ? {} : except(synthetic)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(options)
|
23
|
+
super(options)
|
24
|
+
@banlist = %w[creator created_at modified_at monitor_id public_id].freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def create(body)
|
28
|
+
create_api_resource_name = api_resource_name(body)
|
29
|
+
headers = {}
|
30
|
+
response = api_service.post("/api/#{api_version}/#{create_api_resource_name}", body, headers)
|
31
|
+
resbody = body_with_2xx(response)
|
32
|
+
LOGGER.warn "Successfully created #{resbody.fetch(id_keyname)} in datadog."
|
33
|
+
LOGGER.info 'Invalidating cache'
|
34
|
+
@get_all = nil
|
35
|
+
resbody
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(id, body)
|
39
|
+
update_api_resource_name = api_resource_name(body)
|
40
|
+
headers = {}
|
41
|
+
response = api_service.put("/api/#{api_version}/#{update_api_resource_name}/#{id}", body, headers)
|
42
|
+
resbody = body_with_2xx(response)
|
43
|
+
LOGGER.warn "Successfully restored #{id} to datadog."
|
44
|
+
LOGGER.info 'Invalidating cache'
|
45
|
+
@get_all = nil
|
46
|
+
resbody
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def api_version
|
52
|
+
'v1'
|
53
|
+
end
|
54
|
+
|
55
|
+
def api_resource_name(body = nil)
|
56
|
+
return 'synthetics/tests' if body.nil?
|
57
|
+
return 'synthetics/tests' if body['type'].nil?
|
58
|
+
return 'synthetics/tests/browser' if body['type'].to_s == 'browser'
|
59
|
+
return 'synthetics/tests/api' if body['type'].to_s == 'api'
|
60
|
+
|
61
|
+
raise "Unknown type #{body['type']}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def id_keyname
|
65
|
+
'public_id'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/datadog_backup.rb
CHANGED
@@ -5,15 +5,15 @@ require 'concurrent'
|
|
5
5
|
require_relative 'datadog_backup/local_filesystem'
|
6
6
|
require_relative 'datadog_backup/options'
|
7
7
|
require_relative 'datadog_backup/cli'
|
8
|
-
require_relative 'datadog_backup/
|
8
|
+
require_relative 'datadog_backup/resources'
|
9
9
|
require_relative 'datadog_backup/dashboards'
|
10
10
|
require_relative 'datadog_backup/monitors'
|
11
|
+
require_relative 'datadog_backup/synthetics'
|
11
12
|
require_relative 'datadog_backup/thread_pool'
|
12
13
|
require_relative 'datadog_backup/version'
|
13
14
|
require_relative 'datadog_backup/deprecations'
|
14
15
|
DatadogBackup::Deprecations.check
|
15
16
|
|
16
|
-
|
17
|
+
# DatadogBackup is a gem for backing up and restoring Datadog monitors and dashboards.
|
17
18
|
module DatadogBackup
|
18
19
|
end
|
19
|
-
|
data/release.config.js
CHANGED
@@ -29,16 +29,14 @@ describe DatadogBackup::Cli do
|
|
29
29
|
describe '#backup' do
|
30
30
|
context 'when dashboards are deleted in datadog' do
|
31
31
|
let(:all_dashboards) do
|
32
|
-
|
33
|
-
200,
|
34
|
-
{},
|
32
|
+
respond_with200(
|
35
33
|
{
|
36
34
|
'dashboards' => [
|
37
35
|
{ 'id' => 'stillthere' },
|
38
36
|
{ 'id' => 'alsostillthere' }
|
39
37
|
]
|
40
38
|
}
|
41
|
-
|
39
|
+
)
|
42
40
|
end
|
43
41
|
|
44
42
|
before do
|
@@ -47,8 +45,8 @@ describe DatadogBackup::Cli do
|
|
47
45
|
dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/deleted.json")
|
48
46
|
|
49
47
|
stubs.get('/api/v1/dashboard') { all_dashboards }
|
50
|
-
stubs.get('/api/v1/dashboard/stillthere') {
|
51
|
-
stubs.get('/api/v1/dashboard/alsostillthere') {
|
48
|
+
stubs.get('/api/v1/dashboard/stillthere') { respond_with200({}) }
|
49
|
+
stubs.get('/api/v1/dashboard/alsostillthere') { respond_with200({}) }
|
52
50
|
end
|
53
51
|
|
54
52
|
it 'deletes the file locally as well' do
|
@@ -58,66 +56,89 @@ describe DatadogBackup::Cli do
|
|
58
56
|
end
|
59
57
|
end
|
60
58
|
|
61
|
-
describe '#diffs' do
|
62
|
-
subject { cli.diffs }
|
63
|
-
|
64
|
-
before do
|
65
|
-
dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/diffs1.json")
|
66
|
-
dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/diffs2.json")
|
67
|
-
dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/diffs3.json")
|
68
|
-
allow(dashboards).to receive(:get_by_id).and_return({ 'text' => 'diff2' })
|
69
|
-
end
|
70
|
-
|
71
|
-
it {
|
72
|
-
expect(subject).to include(
|
73
|
-
" ---\n id: diffs1\n ---\n-text: diff2\n+text: diff\n",
|
74
|
-
" ---\n id: diffs3\n ---\n-text: diff2\n+text: diff\n",
|
75
|
-
" ---\n id: diffs2\n ---\n-text: diff2\n+text: diff\n"
|
76
|
-
)
|
77
|
-
}
|
78
|
-
end
|
79
|
-
|
80
59
|
describe '#restore' do
|
81
|
-
subject { cli.restore }
|
60
|
+
subject(:restore) { cli.restore }
|
61
|
+
let(:stdin) { class_double('STDIN') }
|
82
62
|
|
63
|
+
after(:all) do
|
64
|
+
$stdin = STDIN
|
65
|
+
end
|
66
|
+
|
83
67
|
before do
|
68
|
+
$stdin = stdin
|
84
69
|
dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/diffs1.json")
|
85
70
|
allow(dashboards).to receive(:get_by_id).and_return({ 'text' => 'diff2' })
|
71
|
+
allow(dashboards).to receive(:write_file)
|
72
|
+
allow(dashboards).to receive(:update)
|
86
73
|
end
|
87
74
|
|
88
75
|
example 'starts interactive restore' do
|
89
|
-
allow(
|
76
|
+
allow(stdin).to receive(:gets).and_return('q')
|
90
77
|
|
91
|
-
expect {
|
78
|
+
expect { restore }.to(
|
92
79
|
output(/\(r\)estore to Datadog, overwrite local changes and \(d\)ownload, \(s\)kip, or \(q\)uit\?/).to_stdout
|
93
80
|
.and(raise_error(SystemExit))
|
94
81
|
)
|
95
82
|
end
|
96
83
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
84
|
+
context 'when the user chooses to restore' do
|
85
|
+
before do
|
86
|
+
allow(stdin).to receive(:gets).and_return('r')
|
87
|
+
end
|
88
|
+
|
89
|
+
example 'it restores from disk to server' do
|
90
|
+
restore
|
91
|
+
expect(dashboards).to have_received(:update).with('diffs1', { 'text' => 'diff' })
|
92
|
+
end
|
101
93
|
end
|
102
94
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
95
|
+
context 'when the user chooses to download' do
|
96
|
+
before do
|
97
|
+
allow(stdin).to receive(:gets).and_return('d')
|
98
|
+
end
|
99
|
+
|
100
|
+
example 'it writes from server to disk' do
|
101
|
+
restore
|
102
|
+
expect(dashboards).to have_received(:write_file).with(%({\n "text": "diff2"\n}), "#{tempdir}/dashboards/diffs1.json")
|
103
|
+
end
|
107
104
|
end
|
108
105
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
106
|
+
context 'when the user chooses to skip' do
|
107
|
+
before do
|
108
|
+
allow(stdin).to receive(:gets).and_return('s')
|
109
|
+
end
|
110
|
+
|
111
|
+
example 'it does not write to disk' do
|
112
|
+
restore
|
113
|
+
expect(dashboards).not_to have_received(:write_file)
|
114
|
+
end
|
115
|
+
|
116
|
+
example 'it does not update the server' do
|
117
|
+
restore
|
118
|
+
expect(dashboards).not_to have_received(:update)
|
119
|
+
end
|
114
120
|
end
|
115
121
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
122
|
+
context 'when the user chooses to quit' do
|
123
|
+
before do
|
124
|
+
allow(stdin).to receive(:gets).and_return('q')
|
125
|
+
end
|
126
|
+
|
127
|
+
example 'it exits' do
|
128
|
+
expect { restore }.to raise_error(SystemExit)
|
129
|
+
end
|
130
|
+
|
131
|
+
example 'it does not write to disk' do
|
132
|
+
restore
|
133
|
+
rescue SystemExit
|
134
|
+
expect(dashboards).not_to have_received(:write_file)
|
135
|
+
end
|
136
|
+
|
137
|
+
example 'it does not update the server' do
|
138
|
+
restore
|
139
|
+
rescue SystemExit
|
140
|
+
expect(dashboards).not_to have_received(:update)
|
141
|
+
end
|
121
142
|
end
|
122
143
|
end
|
123
144
|
end
|
@@ -2,133 +2,154 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe DatadogBackup::
|
5
|
+
describe DatadogBackup::Resources do
|
6
6
|
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
7
7
|
let(:api_client_double) { Faraday.new { |f| f.adapter :test, stubs } }
|
8
8
|
let(:tempdir) { Dir.mktmpdir }
|
9
|
-
let(:
|
10
|
-
|
9
|
+
let(:resources) do
|
10
|
+
resources = described_class.new(
|
11
11
|
action: 'backup',
|
12
12
|
backup_dir: tempdir,
|
13
13
|
diff_format: nil,
|
14
14
|
resources: [],
|
15
15
|
output_format: :json
|
16
16
|
)
|
17
|
-
allow(
|
18
|
-
return
|
17
|
+
allow(resources).to receive(:api_service).and_return(api_client_double)
|
18
|
+
return resources
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
21
|
describe '#diff' do
|
25
|
-
subject {
|
22
|
+
subject(:diff) { resources.diff('diff') }
|
26
23
|
|
27
24
|
before do
|
28
|
-
allow(
|
29
|
-
|
25
|
+
allow(resources).to receive(:get_by_id).and_return({ 'text' => 'diff1', 'extra' => 'diff1' })
|
26
|
+
resources.write_file('{"text": "diff2", "extra": "diff2"}', "#{tempdir}/resources/diff.json")
|
30
27
|
end
|
31
28
|
|
32
29
|
it {
|
33
|
-
expect(
|
30
|
+
expect(diff).to eq(<<~EODIFF
|
34
31
|
---
|
35
32
|
-extra: diff1
|
36
33
|
-text: diff1
|
37
34
|
+extra: diff2
|
38
35
|
+text: diff2
|
39
|
-
|
36
|
+
EODIFF
|
37
|
+
.chomp)
|
40
38
|
}
|
41
39
|
end
|
42
40
|
|
43
41
|
describe '#except' do
|
44
|
-
subject {
|
42
|
+
subject { resources.except({ a: :b, b: :c }) }
|
45
43
|
|
46
44
|
it { is_expected.to eq({ a: :b, b: :c }) }
|
47
45
|
end
|
48
46
|
|
49
47
|
describe '#initialize' do
|
50
|
-
subject {
|
48
|
+
subject(:myresources) { resources }
|
51
49
|
|
52
50
|
it 'makes the subdirectories' do
|
53
|
-
|
54
|
-
|
51
|
+
fileutils = class_double(FileUtils).as_stubbed_const
|
52
|
+
allow(fileutils).to receive(:mkdir_p)
|
53
|
+
myresources
|
54
|
+
expect(fileutils).to have_received(:mkdir_p).with("#{tempdir}/resources")
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
58
|
describe '#myclass' do
|
59
|
-
subject {
|
59
|
+
subject { resources.myclass }
|
60
60
|
|
61
|
-
it { is_expected.to eq '
|
61
|
+
it { is_expected.to eq 'resources' }
|
62
62
|
end
|
63
63
|
|
64
64
|
describe '#create' do
|
65
|
-
subject {
|
65
|
+
subject(:create) { resources.create({ 'a' => 'b' }) }
|
66
66
|
|
67
67
|
example 'it will post /api/v1/dashboard' do
|
68
|
-
allow(
|
69
|
-
allow(
|
70
|
-
stubs.post('/api/v1/dashboard', {'a' => 'b'}) {
|
71
|
-
|
68
|
+
allow(resources).to receive(:api_version).and_return('v1')
|
69
|
+
allow(resources).to receive(:api_resource_name).and_return('dashboard')
|
70
|
+
stubs.post('/api/v1/dashboard', { 'a' => 'b' }) { respond_with200({ 'id' => 'whatever-id-abc' }) }
|
71
|
+
create
|
72
72
|
stubs.verify_stubbed_calls
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
76
|
describe '#update' do
|
77
|
-
subject {
|
77
|
+
subject(:update) { resources.update('abc-123-def', { 'a' => 'b' }) }
|
78
78
|
|
79
79
|
example 'it puts /api/v1/dashboard' do
|
80
|
-
allow(
|
81
|
-
allow(
|
82
|
-
stubs.put('/api/v1/dashboard/abc-123-def', {'a' => 'b'}) {
|
83
|
-
|
80
|
+
allow(resources).to receive(:api_version).and_return('v1')
|
81
|
+
allow(resources).to receive(:api_resource_name).and_return('dashboard')
|
82
|
+
stubs.put('/api/v1/dashboard/abc-123-def', { 'a' => 'b' }) { respond_with200({ 'id' => 'whatever-id-abc' }) }
|
83
|
+
update
|
84
84
|
stubs.verify_stubbed_calls
|
85
85
|
end
|
86
86
|
|
87
87
|
context 'when the id is not found' do
|
88
88
|
before do
|
89
|
-
allow(
|
90
|
-
allow(
|
91
|
-
stubs.put('/api/v1/dashboard/abc-123-def', {'a' => 'b'}) {[404, {},
|
89
|
+
allow(resources).to receive(:api_version).and_return('v1')
|
90
|
+
allow(resources).to receive(:api_resource_name).and_return('dashboard')
|
91
|
+
stubs.put('/api/v1/dashboard/abc-123-def', { 'a' => 'b' }) { [404, {}, { 'id' => 'whatever-id-abc' }] }
|
92
92
|
end
|
93
|
+
|
93
94
|
it 'raises an error' do
|
94
|
-
expect {
|
95
|
+
expect { update }.to raise_error(RuntimeError, 'update failed with error 404')
|
95
96
|
end
|
96
97
|
end
|
97
98
|
end
|
98
99
|
|
99
100
|
describe '#restore' do
|
100
101
|
before do
|
101
|
-
allow(
|
102
|
-
allow(
|
103
|
-
stubs.get('/api/api-version-string/api-resource-name-string/abc-123-def') {
|
104
|
-
stubs.get('/api/api-version-string/api-resource-name-string/bad-123-id')
|
105
|
-
|
102
|
+
allow(resources).to receive(:api_version).and_return('api-version-string')
|
103
|
+
allow(resources).to receive(:api_resource_name).and_return('api-resource-name-string')
|
104
|
+
stubs.get('/api/api-version-string/api-resource-name-string/abc-123-def') { respond_with200({ 'test' => 'ok' }) }
|
105
|
+
stubs.get('/api/api-version-string/api-resource-name-string/bad-123-id') do
|
106
|
+
[404, {}, { 'error' => 'blahblah_not_found' }]
|
107
|
+
end
|
108
|
+
allow(resources).to receive(:load_from_file_by_id).and_return({ 'load' => 'ok' })
|
106
109
|
end
|
107
110
|
|
108
111
|
context 'when id exists' do
|
109
|
-
subject {
|
112
|
+
subject(:restore) { resources.restore('abc-123-def') }
|
110
113
|
|
111
114
|
example 'it calls out to update' do
|
112
|
-
|
113
|
-
|
115
|
+
allow(resources).to receive(:update)
|
116
|
+
restore
|
117
|
+
expect(resources).to have_received(:update).with('abc-123-def', { 'load' => 'ok' })
|
114
118
|
end
|
115
119
|
end
|
116
120
|
|
117
121
|
context 'when id does not exist on remote' do
|
118
|
-
subject {
|
122
|
+
subject(:restore_newly) { resources.restore('bad-123-id') }
|
123
|
+
|
124
|
+
let(:fileutils) { class_double(FileUtils).as_stubbed_const }
|
119
125
|
|
120
126
|
before do
|
121
|
-
allow(
|
122
|
-
stubs.put('/api/api-version-string/api-resource-name-string/bad-123-id')
|
123
|
-
|
127
|
+
allow(resources).to receive(:load_from_file_by_id).and_return({ 'load' => 'ok' })
|
128
|
+
stubs.put('/api/api-version-string/api-resource-name-string/bad-123-id') do
|
129
|
+
[404, {}, { 'error' => 'id not found' }]
|
130
|
+
end
|
131
|
+
stubs.post('/api/api-version-string/api-resource-name-string', { 'load' => 'ok' }) do
|
132
|
+
respond_with200({ 'id' => 'my-new-id' })
|
133
|
+
end
|
134
|
+
allow(fileutils).to receive(:rm)
|
135
|
+
allow(resources).to receive(:create).with({ 'load' => 'ok' }).and_return({ 'id' => 'my-new-id' })
|
136
|
+
allow(resources).to receive(:get_and_write_file)
|
137
|
+
allow(resources).to receive(:find_file_by_id).with('bad-123-id').and_return('/path/to/bad-123-id.json')
|
138
|
+
end
|
139
|
+
|
140
|
+
example 'it calls out to create' do
|
141
|
+
restore_newly
|
142
|
+
expect(resources).to have_received(:create).with({ 'load' => 'ok' })
|
143
|
+
end
|
144
|
+
|
145
|
+
example 'it saves the new file' do
|
146
|
+
restore_newly
|
147
|
+
expect(resources).to have_received(:get_and_write_file).with('my-new-id')
|
124
148
|
end
|
125
149
|
|
126
|
-
example 'it
|
127
|
-
|
128
|
-
expect(
|
129
|
-
allow(core).to receive(:find_file_by_id).with('bad-123-id').and_return('/path/to/bad-123-id.json')
|
130
|
-
expect(FileUtils).to receive(:rm).with('/path/to/bad-123-id.json')
|
131
|
-
subject
|
150
|
+
example 'it deletes the old file' do
|
151
|
+
restore_newly
|
152
|
+
expect(fileutils).to have_received(:rm).with('/path/to/bad-123-id.json')
|
132
153
|
end
|
133
154
|
end
|
134
155
|
end
|