duple 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module Duple
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Duple::CLI::Config do
4
+ it 'prints the configuration' do
5
+ result = capture_stdout do
6
+ script = Duple::CLI::Config.new
7
+ script.invoke(:all, [], {})
8
+ end
9
+
10
+ result.should =~ /Environments/
11
+ result.should =~ /Groups/
12
+ result.should =~ /Pre-Refresh Tasks/
13
+ result.should =~ /Post-Refresh Tasks/
14
+ result.should =~ /Other Options/
15
+ end
16
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe Duple::CLI::Copy do
4
+ include Duple::CLISpecHelpers
5
+
6
+ def invoke_copy(options = nil)
7
+ options ||= {}
8
+ options = { tables: ['categories'] }.merge(options)
9
+ invoke_cli(:copy, options)
10
+ end
11
+
12
+ before {
13
+ stub_fetch_config
14
+ stub_dump_data
15
+ stub_restore_data
16
+ }
17
+
18
+ context 'with neither tables nor group option' do
19
+ it 'fetches the source credentials' do
20
+ expect {
21
+ invoke_copy(tables: nil)
22
+ }.to raise_error(ArgumentError, 'One of --group or --tables options is required.')
23
+ end
24
+ end
25
+
26
+ context 'from heroku to local' do
27
+ let(:source) { 'stage' }
28
+ let(:target) { 'development' }
29
+
30
+ it 'fetches the source credentials' do
31
+ runner.should_receive(:capture).with("heroku config -a duple-stage")
32
+ .and_return(heroku_config_response)
33
+
34
+ invoke_copy
35
+ end
36
+
37
+ it 'dowloads the data from the source' do
38
+ runner.should_receive(:run)
39
+ .with(%{PGPASSWORD="pg-pass" pg_dump -Fc -a -t categories -h pg-host -U pg-user -p 6022 pg-db > tmp/duple/stage-data.dump})
40
+
41
+ invoke_copy
42
+ end
43
+
44
+ it 'uploads the data to the target' do
45
+ runner.should_receive(:run)
46
+ .with(%{PGPASSWORD="" pg_restore -e -v --no-acl -O -a -h localhost -U postgres -p 5432 -d duple_development < tmp/duple/stage-data.dump})
47
+
48
+ invoke_copy
49
+ end
50
+ end
51
+
52
+ context 'from heroku to heroku' do
53
+ let(:source) { 'production' }
54
+ let(:target) { 'stage' }
55
+
56
+ it 'fetches the source credentials' do
57
+ runner.should_receive(:capture).with("heroku config -a duple-production")
58
+ .and_return(heroku_config_response)
59
+
60
+ invoke_copy
61
+ end
62
+
63
+ it 'fetches the target credentials' do
64
+ runner.should_receive(:capture).with("heroku config -a duple-stage")
65
+ .and_return(heroku_config_response)
66
+
67
+ invoke_copy
68
+ end
69
+
70
+ it 'dowloads the data from the source' do
71
+ runner.should_receive(:run)
72
+ .with(%{PGPASSWORD="pg-pass" pg_dump -Fc -a -t categories -h pg-host -U pg-user -p 6022 pg-db > tmp/duple/production-data.dump})
73
+
74
+ invoke_copy
75
+ end
76
+
77
+ it 'uploads the data to the target' do
78
+ runner.should_receive(:run)
79
+ .with(%{PGPASSWORD="pg-pass" pg_restore -e -v --no-acl -O -a -h pg-host -U pg-user -p 6022 -d pg-db < tmp/duple/production-data.dump})
80
+
81
+ invoke_copy
82
+ end
83
+ end
84
+
85
+ context 'from local to heroku' do
86
+ let(:source) { 'development' }
87
+ let(:target) { 'stage' }
88
+
89
+ it 'fetches the target credentials' do
90
+ runner.should_receive(:capture).with("heroku config -a duple-stage")
91
+ .and_return(heroku_config_response)
92
+
93
+ invoke_copy
94
+ end
95
+
96
+ it 'dumps the data from the local db' do
97
+ stub_fetch_config
98
+ runner.should_receive(:run)
99
+ .with(%{PGPASSWORD="" pg_dump -Fc -a -t categories -h localhost -U postgres -p 5432 duple_development > tmp/duple/development-data.dump})
100
+ stub_restore_data
101
+
102
+ invoke_copy
103
+ end
104
+
105
+ it 'uploads the data to the target' do
106
+ runner.should_receive(:run)
107
+ .with(%{PGPASSWORD="pg-pass" pg_restore -e -v --no-acl -O -a -h pg-host -U pg-user -p 6022 -d pg-db < tmp/duple/development-data.dump})
108
+
109
+ invoke_copy
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Duple::CLI::Init do
4
+ before { suppress_output }
5
+ after { reset_output }
6
+
7
+ before { FileUtils.mkdir_p('tmp/duple/init') }
8
+ after { FileUtils.rm_rf('tmp/duple') }
9
+
10
+ it 'creates a config file in the default location' do
11
+ FileUtils.chdir('tmp/duple/init') do
12
+ # capture_stdout do
13
+ script = Duple::CLI::Root.new
14
+ script.invoke(:init)
15
+ Pathname.new("config/duple.yml").should exist
16
+ # end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,413 @@
1
+ require 'spec_helper'
2
+
3
+ describe Duple::CLI::Refresh do
4
+ include Duple::CLISpecHelpers
5
+
6
+ def invoke_refresh(options = nil)
7
+ invoke_cli(:refresh, options)
8
+ end
9
+
10
+ before { FileUtils.mkdir_p('tmp/duple') }
11
+ after { FileUtils.rm_rf('tmp/duple') }
12
+
13
+ context 'from heroku to heroku' do
14
+ before {
15
+ stub_fetch_url
16
+ stub_reset_heroku
17
+ stub_restore_url
18
+ }
19
+
20
+ let(:source) { 'production' }
21
+ let(:target) { 'stage' }
22
+
23
+ it 'runs commands in the correct order' do
24
+ runner.should_receive(:run).once.ordered.with(/heroku pgbackups:capture/)
25
+ runner.should_receive(:capture).once.ordered.with(/heroku pgbackups:url/).and_return(heroku_pgbackups_url_response)
26
+ runner.should_receive(:run).once.ordered.with(/heroku pg:reset/)
27
+ runner.should_receive(:run).once.ordered.with(/heroku pgbackups:restore/)
28
+
29
+ invoke_refresh(capture: true)
30
+ end
31
+
32
+ it 'fetches the latest snapshot URL for the source' do
33
+ runner.should_receive(:capture).with('heroku pgbackups:url -a duple-production')
34
+ .and_return(heroku_pgbackups_url_response)
35
+
36
+ invoke_refresh
37
+ end
38
+
39
+ it 'resets the target database' do
40
+ runner.should_receive(:run).with(%{heroku pg:reset -a duple-stage})
41
+
42
+ invoke_refresh
43
+ end
44
+
45
+ it 'does not reset the source database' do
46
+ runner.should_not_receive(:run).with(/heroku pg:reset -a phsnap-production/)
47
+
48
+ invoke_refresh
49
+ end
50
+
51
+ it 'restores the target from the snapshot URL' do
52
+ db_url = heroku_pgbackups_url_response.strip
53
+ runner.should_receive(:run).with(%{heroku pgbackups:restore DATABASE #{db_url} -a duple-stage})
54
+
55
+ invoke_refresh
56
+ end
57
+
58
+ it 'does not capture a new snapshot by default' do
59
+ runner.should_not_receive(:run).with(/heroku pgbackups:capture/)
60
+ invoke_refresh
61
+ end
62
+
63
+ context 'with the --capture flag' do
64
+ it 'captures a new snapshot before fetching the URL' do
65
+ runner.should_receive(:run).with('heroku pgbackups:capture -a duple-production')
66
+ invoke_refresh(capture: true)
67
+ end
68
+ end
69
+
70
+ context 'with a table list' do
71
+ before {
72
+ stub_fetch_config
73
+ stub_dump_data
74
+ stub_restore_data
75
+ }
76
+
77
+ it 'runs commands in the correct order' do
78
+ runner.should_not_receive(:capture).with(/heroku pgbackups:url/)
79
+ runner.should_not_receive(:run).with(/heroku pgbackups:restore/)
80
+
81
+ runner.should_receive(:run).once.ordered.with(/heroku pgbackups:capture/)
82
+ runner.should_receive(:capture).once.ordered.with(/heroku config -a duple-production/).and_return(heroku_config_response)
83
+ runner.should_receive(:run).once.ordered.with(/pg_dump/).and_return(heroku_config_response)
84
+ runner.should_receive(:run).once.ordered.with(/heroku pg:reset/)
85
+ runner.should_receive(:capture).once.ordered.with(/heroku config -a duple-stage/).and_return(heroku_config_response)
86
+ runner.should_receive(:run).once.ordered.with(/pg_restore/)
87
+
88
+ invoke_refresh(capture: true, tables: ['categories'])
89
+ end
90
+
91
+ it 'does not download the snapshot' do
92
+ runner.should_not_receive(:capture).with(/pgbackups/)
93
+ runner.should_not_receive(:capture).with(/curl/)
94
+
95
+ invoke_refresh(tables: ['categories'])
96
+ end
97
+
98
+ it 'dumps the tables in the list' do
99
+ runner.should_receive(:run)
100
+ .with(%{PGPASSWORD="pg-pass" pg_dump -Fc -a -t categories -h pg-host -U pg-user -p 6022 pg-db > tmp/duple/production-data.dump})
101
+
102
+ invoke_refresh(tables: ['categories'])
103
+ end
104
+
105
+ it 'loads the dump file into the target database' do
106
+ runner.should_receive(:run)
107
+ .with(%{PGPASSWORD="pg-pass" pg_restore -e -v --no-acl -O -a -h pg-host -U pg-user -p 6022 -d pg-db < tmp/duple/production-data.dump})
108
+
109
+ invoke_refresh(tables: ['categories'])
110
+ end
111
+ end
112
+ end
113
+
114
+ context 'from heroku to local' do
115
+ before {
116
+ stub_fetch_url
117
+ stub_fetch_backups
118
+ stub_download_snapshot
119
+ stub_reset_local
120
+ stub_restore_data
121
+ }
122
+
123
+ let(:source) { 'stage' }
124
+ let(:target) { 'development' }
125
+
126
+ it 'runs commands in the correct order' do
127
+ runner.should_receive(:run).once.ordered.with(/heroku pgbackups:capture/)
128
+ runner.should_receive(:capture).once.ordered.with(/heroku pgbackups:url/).and_return(heroku_pgbackups_url_response)
129
+ runner.should_receive(:capture).once.ordered.with(/heroku pgbackups/).and_return(heroku_pgbackups_response)
130
+ runner.should_receive(:run).once.ordered.with(/curl/)
131
+ runner.should_receive(:run).once.ordered.with(/rake db:drop db:create/)
132
+ runner.should_receive(:run).once.ordered.with(/pg_restore/)
133
+
134
+ invoke_refresh(capture: true)
135
+ end
136
+
137
+ it 'fetches the latest snapshot URL for the source' do
138
+ runner.should_receive(:capture).with('heroku pgbackups:url -a duple-stage')
139
+ .and_return(heroku_pgbackups_url_response)
140
+
141
+ invoke_refresh
142
+ end
143
+
144
+ it 'resets the local database' do
145
+ runner.should_receive(:run).with(%{bundle exec rake db:drop db:create})
146
+
147
+ invoke_refresh
148
+ end
149
+
150
+ it 'does not reset the source database' do
151
+ runner.should_not_receive(:run).with(/heroku pg:reset/)
152
+
153
+ invoke_refresh
154
+ end
155
+
156
+ it 'downloads the snapshot from the snapshot URL' do
157
+ runner.should_receive(:run).with(%{curl -o #{snapshot_path} #{heroku_pgbackups_url_response.strip}})
158
+
159
+ invoke_refresh
160
+ end
161
+
162
+ it 'does not re-download the snapshot if the timestamp has not changed' do
163
+ FileUtils.touch(snapshot_path)
164
+
165
+ runner.should_not_receive(:run).with(/curl/)
166
+
167
+ invoke_refresh
168
+ end
169
+
170
+ it 'loads the snapshot file into the local database' do
171
+ runner.should_receive(:run)
172
+ .with(%{PGPASSWORD="" pg_restore -e -v --no-acl -O -a -h localhost -U postgres -p 5432 -d duple_development < #{snapshot_path}})
173
+
174
+ invoke_refresh
175
+ end
176
+
177
+ it 'does not capture a new snapshot by default' do
178
+ runner.should_not_receive(:run).with(/heroku pgbackups:capture/)
179
+ invoke_refresh
180
+ end
181
+
182
+ context 'with the --capture flag' do
183
+ it 'captures a new snapshot before fetching the URL' do
184
+ runner.should_receive(:run).with('heroku pgbackups:capture -a duple-stage')
185
+ invoke_refresh(capture: true)
186
+ end
187
+ end
188
+
189
+ context 'with a table list' do
190
+ let(:table_options) { {tables: ['categories']} }
191
+
192
+ before {
193
+ stub_fetch_config
194
+ stub_dump_data
195
+ stub_restore_data
196
+ }
197
+
198
+ it 'runs commands in the correct order' do
199
+ runner.should_receive(:run).once.ordered.with(/heroku pgbackups:capture/)
200
+ runner.should_receive(:capture).once.ordered.with(/heroku config -a duple-stage/).and_return(heroku_config_response)
201
+ runner.should_receive(:run).once.ordered.with(/pg_dump/)
202
+ runner.should_receive(:run).once.ordered.with(/rake db:drop db:create/)
203
+ runner.should_receive(:run).once.ordered.with(/pg_restore/)
204
+
205
+ invoke_refresh(table_options.merge(capture: true))
206
+ end
207
+
208
+ it 'does not download the snapshot' do
209
+ runner.should_not_receive(:capture).with(/pgbackups/)
210
+ runner.should_not_receive(:capture).with(/curl/)
211
+
212
+ invoke_refresh(table_options)
213
+ end
214
+
215
+ it 'dumps the tables in the list' do
216
+ runner.should_receive(:run)
217
+ .with(%{PGPASSWORD="pg-pass" pg_dump -Fc -a -t categories -h pg-host -U pg-user -p 6022 pg-db > tmp/duple/stage-data.dump})
218
+
219
+ invoke_refresh(table_options)
220
+ end
221
+
222
+ it 'loads the dump file into the target database' do
223
+ runner.should_receive(:run)
224
+ .with(%{PGPASSWORD="" pg_restore -e -v --no-acl -O -a -h localhost -U postgres -p 5432 -d duple_development < tmp/duple/stage-data.dump})
225
+
226
+ invoke_refresh(table_options)
227
+ end
228
+ end
229
+ end
230
+
231
+ context 'from local to heroku' do
232
+ before {
233
+ stub_dump_data
234
+ stub_reset_heroku
235
+ stub_fetch_config
236
+ stub_restore_data
237
+ }
238
+
239
+ let(:source) { 'development' }
240
+ let(:target) { 'stage' }
241
+
242
+ it 'runs commands in the correct order' do
243
+ runner.should_receive(:run).once.ordered.with(/pg_dump/)
244
+ runner.should_receive(:run).once.ordered.with(/heroku pg:reset/)
245
+ runner.should_receive(:capture).once.ordered.with(/heroku config -a duple-stage/).and_return(heroku_config_response)
246
+ runner.should_receive(:run).once.ordered.with(/pg_restore/)
247
+
248
+ invoke_refresh(capture: true)
249
+ end
250
+
251
+ it 'dumps the data from the local database' do
252
+ runner.should_receive(:run)
253
+ .with(%{PGPASSWORD="" pg_dump -Fc -a -h localhost -U postgres -p 5432 duple_development > tmp/duple/development-data.dump})
254
+
255
+ invoke_refresh
256
+ end
257
+
258
+ it 'resets the target database' do
259
+ runner.should_receive(:run).with(%{heroku pg:reset -a duple-stage})
260
+
261
+ invoke_refresh
262
+ end
263
+
264
+ it 'restores the target from the dump' do
265
+ runner.should_receive(:run)
266
+ .with(%{PGPASSWORD="pg-pass" pg_restore -e -v --no-acl -O -a -h pg-host -U pg-user -p 6022 -d pg-db < tmp/duple/development-data.dump})
267
+
268
+ invoke_refresh
269
+ end
270
+
271
+ it 'does not capture a new snapshot' do
272
+ runner.should_not_receive(:run).with(/heroku pgbackups:capture/)
273
+
274
+ invoke_refresh
275
+ end
276
+
277
+ context 'with the --capture flag' do
278
+ it 'does not capture a new snapshot' do
279
+ runner.should_not_receive(:run).with(/heroku pgbackups:capture/)
280
+
281
+ invoke_refresh(capture: true)
282
+ end
283
+ end
284
+
285
+ context 'with a table list' do
286
+ it 'downloads only the tables in the list' do
287
+ runner.should_receive(:run)
288
+ .with(%{PGPASSWORD="" pg_dump -Fc -a -t categories -h localhost -U postgres -p 5432 duple_development > tmp/duple/development-data.dump})
289
+
290
+ invoke_refresh(tables: ['categories'])
291
+ end
292
+ end
293
+ end
294
+
295
+ context 'with pre-refresh tasks' do
296
+ before { pending 'Implement pre-refresh tasks' }
297
+
298
+ before {
299
+ stub_fetch_url
300
+ stub_fetch_config
301
+ stub_fetch_backups
302
+ stub_dump_data
303
+ stub_download_snapshot
304
+ stub_reset_heroku
305
+ stub_reset_local
306
+ stub_restore_url
307
+ stub_restore_data
308
+ }
309
+
310
+ let(:source) { 'production' }
311
+ let(:target) { 'stage' }
312
+ let(:task_options) { { config: 'spec/config/tasks.yml' } }
313
+
314
+ it 'executes the tasks before refreshing' do
315
+ runner.should_receive(:run).once.ordered.with('heroku maintenance:on -a duple-stage')
316
+ runner.should_receive(:run).any_number_of_times.ordered.with(/heroku pgbackups/)
317
+
318
+ invoke_refresh(table_options.merge(capture: true))
319
+ end
320
+
321
+ it 'executes the tasks in order' do
322
+ runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:prepare" -a duple-production')
323
+ runner.should_receive(:run).once.ordered.with('heroku maintenance:on -a duple-stage')
324
+ runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:prepare" -a duple-stage')
325
+
326
+ invoke_refresh(task_options)
327
+ end
328
+
329
+ context 'with a local target' do
330
+ let(:target) { 'development' }
331
+
332
+ it 'executes the tasks in order' do
333
+ runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:prepare" -a duple-production')
334
+ runner.should_not_receive(:run).with(/heroku maintenance:on/)
335
+ runner.should_receive(:run).once.ordered.with('rake refresh:prepare')
336
+
337
+ invoke_refresh(task_options)
338
+ end
339
+ end
340
+
341
+ context 'with a local source' do
342
+ let(:source) { 'development' }
343
+
344
+ it 'executes the tasks in order' do
345
+ runner.should_receive(:run).once.ordered.with('rake refresh:prepare')
346
+ runner.should_receive(:run).once.ordered.with('heroku maintenance:on -a duple-stage')
347
+ runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:prepare" -a duple-stage')
348
+
349
+ invoke_refresh(task_options)
350
+ end
351
+ end
352
+ end
353
+
354
+ context 'with post-refresh tasks' do
355
+ before { pending 'Implement post-refresh tasks' }
356
+ before {
357
+ stub_fetch_url
358
+ stub_fetch_config
359
+ stub_fetch_backups
360
+ stub_dump_data
361
+ stub_download_snapshot
362
+ stub_reset_heroku
363
+ stub_reset_local
364
+ stub_restore_url
365
+ stub_restore_data
366
+ }
367
+
368
+ let(:source) { 'production' }
369
+ let(:target) { 'stage' }
370
+ let(:task_options) { { config: 'spec/config/tasks.yml' } }
371
+
372
+ it 'executes the tasks after refreshing' do
373
+ runner.should_receive(:run).any_number_of_times.ordered.with(/heroku pgbackups/)
374
+ runner.should_receive(:run).once.ordered.with('heroku maintenance:on -a duple-stage')
375
+
376
+ invoke_refresh(table_options.merge(capture: true))
377
+ end
378
+
379
+ it 'executes the tasks in order' do
380
+ runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:finish" -a duple-production')
381
+ runner.should_receive(:run).once.ordered.with('heroku maintenance:off -a duple-stage')
382
+ runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:finish" -a duple-stage')
383
+
384
+ invoke_refresh(task_options)
385
+ end
386
+
387
+ context 'with a local target' do
388
+ let(:target) { 'development' }
389
+
390
+ it 'executes the tasks in order' do
391
+ runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:finish" -a duple-production')
392
+ runner.should_not_receive(:run).with(/heroku maintenance:off/)
393
+ runner.should_receive(:run).once.ordered.with('rake refresh:finish')
394
+
395
+ invoke_refresh(task_options)
396
+ end
397
+ end
398
+
399
+ context 'with a local source' do
400
+ let(:source) { 'development' }
401
+
402
+ it 'executes the tasks in order' do
403
+ runner.should_receive(:run).once.ordered.with('rake refresh:finish')
404
+ runner.should_receive(:run).once.ordered.with('heroku maintenance:on -a duple-stage')
405
+ runner.should_receive(:run).once.ordered.with('heroku run "rake refresh:finish" -a duple-stage')
406
+
407
+ invoke_refresh(task_options)
408
+ end
409
+ end
410
+ end
411
+
412
+
413
+ end