kitchen-scribe 0.1.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
+ #
2
+ # Author:: Pawel Kozlowski (<pawel.kozlowski@u2i.com>)
3
+ # Copyright:: Copyright (c) 2013 Pawel Kozlowski
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+
20
+ require 'chef/mixin/shell_out'
21
+
22
+ class Chef
23
+ class Knife
24
+ class ScribeHire < Chef::Knife
25
+
26
+ include Chef::Mixin::ShellOut
27
+
28
+ DEFAULT_CHRONICLE_PATH = ".chronicle"
29
+ DEFAULT_REMOTE_NAME = "origin"
30
+
31
+ banner "knife scribe hire"
32
+
33
+ option :chronicle_path,
34
+ :short => "-p PATH",
35
+ :long => "--chronicle-path PATH",
36
+ :description => "Path to the directory where the chronicle should be located",
37
+ :default => nil
38
+
39
+ option :remote_name,
40
+ :long => "--remote-name REMOTE_NAME",
41
+ :description => "Name of the remote chronicle repository",
42
+ :default => nil
43
+
44
+ option :remote_url,
45
+ :short => "-r REMOTE_URL",
46
+ :long => "--remote-url REMOTE_URL",
47
+ :description => "Url of the remote chronicle repository",
48
+ :default => nil
49
+
50
+ def run
51
+ configure
52
+ Dir.mkdir(config[:chronicle_path]) unless File.directory?(config[:chronicle_path])
53
+ init_chronicle
54
+ setup_remote if config[:remote_url]
55
+ ["environments", "nodes", "roles"].each do |dir|
56
+ path = File.join(config[:chronicle_path], dir)
57
+ Dir.mkdir(path) unless File.directory?(path)
58
+ end
59
+ end
60
+
61
+ def configure
62
+ conf = { :chronicle_path => DEFAULT_CHRONICLE_PATH,
63
+ :remote_name => DEFAULT_REMOTE_NAME }
64
+ conf.merge!(Chef::Config[:knife][:scribe]) if Chef::Config[:knife][:scribe].kind_of? Hash
65
+ conf.each do |key, value|
66
+ config[key] ||= value
67
+ end
68
+ end
69
+
70
+ def init_chronicle
71
+ shell_out!("git init", { :cwd => config[:chronicle_path] })
72
+ end
73
+
74
+ def setup_remote
75
+ check_remote_command = "git config --get remote.#{config[:remote_name]}.url"
76
+ remote_status = shell_out!(check_remote_command, { :cwd => config[:chronicle_path], :returns => [0,1,2] })
77
+ case remote_status.exitstatus
78
+ when 0, 2
79
+ # In theory 2 should not happen unless somebody messed with
80
+ # the checkout manually, but using --replace-all option will fix it
81
+ unless remote_status.exitstatus != 2 && remote_status.stdout.strip.eql?(config[:remote_url])
82
+ update_remote_url_command = "git config --replace-all remote.#{config[:remote_name]}.url #{config[:remote_url]}"
83
+ shell_out!(update_remote_url_command, { :cwd => config[:chronicle_path] })
84
+ end
85
+ when 1
86
+ add_remote_command = "git remote add #{config[:remote_name]} #{config[:remote_url]}"
87
+ shell_out!(add_remote_command, { :cwd => config[:chronicle_path] })
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,4 @@
1
+ module KitchenScribe
2
+ VERSION = "0.1.0"
3
+ MAJOR, MINOR, TINY = VERSION.split('.')
4
+ end
@@ -0,0 +1,492 @@
1
+ #
2
+ # Author:: Pawel Kozlowski (<pawel.kozlowski@u2i.com>)
3
+ # Copyright:: Copyright (c) 2013 Pawel Kozlowski
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require File.expand_path('../../../spec_helper', __FILE__)
20
+
21
+ describe Chef::Knife::ScribeCopy do
22
+ before(:each) do
23
+ @scribe = Chef::Knife::ScribeCopy.new
24
+ @scribe.configure
25
+ end
26
+
27
+ describe "#run" do
28
+ before(:each) do
29
+ @scribe.stub(:remote_configured?)
30
+ @scribe.stub(:pull)
31
+ @scribe.stub(:fetch_configs)
32
+ @scribe.stub(:commit)
33
+ @scribe.stub(:push)
34
+ @scribe.stub(:configure)
35
+ @scribe.stub(:switch_branches)
36
+ @scribe.stub(:fetch)
37
+ end
38
+
39
+ it "configures itself" do
40
+ @scribe.should_receive(:configure)
41
+ @scribe.run
42
+ end
43
+
44
+ it "switches the branch" do
45
+ @scribe.should_receive(:switch_branches)
46
+ @scribe.run
47
+ end
48
+
49
+ it "checks if a given remote is configured" do
50
+ @scribe.should_receive(:remote_configured?)
51
+ @scribe.run
52
+ end
53
+
54
+ it "fetches the configs from the chef server" do
55
+ @scribe.should_receive(:fetch_configs)
56
+ @scribe.run
57
+ end
58
+
59
+ it "commits the changes" do
60
+ @scribe.should_receive(:commit)
61
+ @scribe.run
62
+ end
63
+
64
+ describe "when the remote is not configured" do
65
+ before(:each) do
66
+ @scribe.stub(:remote_configured?) { false }
67
+ end
68
+
69
+ it "doesn't attempt to fetch from the remote repository" do
70
+ @scribe.should_not_receive(:fetch)
71
+ @scribe.run
72
+ end
73
+
74
+ it "doesn't attempt to pull the changes from the remote repository" do
75
+ @scribe.should_not_receive(:pull)
76
+ @scribe.run
77
+ end
78
+
79
+ it "doesn't attempt to push the changes to the remote repository" do
80
+ @scribe.should_not_receive(:push)
81
+ @scribe.run
82
+ end
83
+
84
+ end
85
+
86
+ describe "when the remote is configured" do
87
+ before(:each) do
88
+ @scribe.stub(:remote_configured?) { true }
89
+ end
90
+
91
+ it "fetches changes from remote repository" do
92
+ @scribe.should_receive(:fetch)
93
+ @scribe.run
94
+ end
95
+
96
+ it "pulls changes from remote repository" do
97
+ @scribe.should_receive(:pull)
98
+ @scribe.run
99
+ end
100
+
101
+ it "pushes the changes to the remote repository" do
102
+ @scribe.should_receive(:push)
103
+ @scribe.run
104
+ end
105
+ end
106
+ end
107
+
108
+ describe "#configure" do
109
+
110
+ describe "when no configuration is given" do
111
+ before(:each) do
112
+ @scribe.config = {}
113
+ Chef::Config[:knife][:scribe] = nil
114
+ end
115
+
116
+ it "uses the default values for all parameters" do
117
+ @scribe.configure
118
+ @scribe.config[:chronicle_path].should == Chef::Knife::ScribeCopy::DEFAULT_CHRONICLE_PATH
119
+ @scribe.config[:remote_name].should == Chef::Knife::ScribeCopy::DEFAULT_REMOTE_NAME
120
+ @scribe.config[:branch].should == Chef::Knife::ScribeCopy::DEFAULT_BRANCH
121
+ @scribe.config[:commit_message].should == Chef::Knife::ScribeCopy::DEFAULT_COMMIT_MESSAGE
122
+ end
123
+ end
124
+
125
+ describe "when configuration is given through knife config" do
126
+ before(:each) do
127
+ Chef::Config[:knife][:scribe] = {}
128
+ Chef::Config[:knife][:scribe][:chronicle_path] = Chef::Knife::ScribeCopy::DEFAULT_CHRONICLE_PATH + "_knife"
129
+ Chef::Config[:knife][:scribe][:remote_name] = Chef::Knife::ScribeCopy::DEFAULT_REMOTE_NAME + "_knife"
130
+ Chef::Config[:knife][:scribe][:branch] = Chef::Knife::ScribeCopy::DEFAULT_BRANCH + "_knife"
131
+ Chef::Config[:knife][:scribe][:commit_message] = Chef::Knife::ScribeCopy::DEFAULT_COMMIT_MESSAGE + "_knife"
132
+ @scribe.config = {}
133
+ end
134
+
135
+ describe "when no other configuration is given" do
136
+ before(:each) do
137
+ @scribe.config = {}
138
+ end
139
+
140
+ it "uses the configuration from knife config" do
141
+ @scribe.configure
142
+ @scribe.config[:chronicle_path].should == Chef::Config[:knife][:scribe][:chronicle_path]
143
+ @scribe.config[:remote_name].should == Chef::Config[:knife][:scribe][:remote_name]
144
+ @scribe.config[:branch].should == Chef::Config[:knife][:scribe][:branch]
145
+ @scribe.config[:commit_message].should == Chef::Config[:knife][:scribe][:commit_message]
146
+ end
147
+ end
148
+
149
+ describe "when command line configuration is given" do
150
+ before(:each) do
151
+ @scribe.config[:chronicle_path] = Chef::Knife::ScribeCopy::DEFAULT_CHRONICLE_PATH + "_cmd"
152
+ @scribe.config[:remote_name] = Chef::Knife::ScribeCopy::DEFAULT_REMOTE_NAME + "_cmd"
153
+ @scribe.config[:branch] = Chef::Knife::ScribeCopy::DEFAULT_BRANCH + "_cmd"
154
+ @scribe.config[:commit_message] = Chef::Knife::ScribeCopy::DEFAULT_COMMIT_MESSAGE + "_cmd"
155
+ end
156
+
157
+ it "uses the configuration from command line" do
158
+ @scribe.configure
159
+ @scribe.config[:chronicle_path].should == Chef::Knife::ScribeCopy::DEFAULT_CHRONICLE_PATH + "_cmd"
160
+ @scribe.config[:remote_name].should == Chef::Knife::ScribeCopy::DEFAULT_REMOTE_NAME + "_cmd"
161
+ @scribe.config[:branch].should == Chef::Knife::ScribeCopy::DEFAULT_BRANCH + "_cmd"
162
+ @scribe.config[:commit_message].should == Chef::Knife::ScribeCopy::DEFAULT_COMMIT_MESSAGE + "_cmd"
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ describe "#switch_branches" do
169
+
170
+ before(:each) do
171
+ @command_response = double('shell_out')
172
+ @command_response.stub(:exitstatus) { 0 }
173
+ @branch_command = "git branch"
174
+ end
175
+
176
+ describe "when already on the branch" do
177
+ it "does nothing" do
178
+ @command_response.stub(:stdout) { "#{@scribe.config[:branch]}2\n* #{@scribe.config[:branch]}\n#a{@scribe.config[:branch]}" }
179
+ @scribe.should_receive(:shell_out!).with(@branch_command,
180
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
181
+ switch_command = "git checkout -B #{@scribe.config[:branch]}"
182
+ @scribe.should_not_receive(:shell_out!).with(switch_command,
183
+ :cwd => @scribe.config[:chronicle_path])
184
+ @scribe.switch_branches
185
+ end
186
+ end
187
+
188
+ describe "when the branch exists but is not the current one" do
189
+ it "switches to the branch" do
190
+ @command_response.stub(:stdout) { "#{@scribe.config[:branch]}2\n #{@scribe.config[:branch]}\n#* a{@scribe.config[:branch]}" }
191
+ @scribe.should_receive(:shell_out!).with(@branch_command,
192
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
193
+ switch_command = "git checkout -B #{@scribe.config[:branch]}"
194
+ @scribe.should_receive(:shell_out!).with(switch_command,
195
+ :cwd => @scribe.config[:chronicle_path])
196
+ @scribe.switch_branches
197
+ end
198
+ end
199
+
200
+ describe "when the branch doesn't exist'" do
201
+ it "creates the branch and switches to it" do
202
+ @command_response.stub(:stdout) { "* #{@scribe.config[:branch]}2\n#a{@scribe.config[:branch]}" }
203
+ @scribe.should_receive(:shell_out!).with(@branch_command,
204
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
205
+ switch_command = "git checkout -B #{@scribe.config[:branch]}"
206
+ @scribe.should_receive(:shell_out!).with(switch_command,
207
+ :cwd => @scribe.config[:chronicle_path])
208
+ @scribe.switch_branches
209
+ end
210
+ end
211
+ end
212
+
213
+
214
+ describe "#remote_configured?" do
215
+
216
+ before(:each) do
217
+ @command_response = double('shell_out')
218
+ @command_response.stub(:exitstatus) { 0 }
219
+ @remote_command = "git remote"
220
+ end
221
+
222
+ it "returns false if no remote is configured" do
223
+ @command_response.stub(:stdout) { "" }
224
+ @scribe.should_receive(:shell_out!).with(@remote_command,
225
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
226
+ @scribe.remote_configured?.should be(false)
227
+ end
228
+
229
+
230
+ it "returns false if a given remote is not configured" do
231
+ @command_response.stub(:stdout) { "another_remote_name\nyet_another_remote_name\nAAA#{@scribe.config[:remote_name]}" }
232
+ @scribe.should_receive(:shell_out!).with(@remote_command,
233
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
234
+
235
+ @scribe.remote_configured?.should be(false)
236
+ end
237
+
238
+ it "returns true if a given remote is configured" do
239
+ @command_response.stub(:stdout) { "another_#{@scribe.config[:remote_name]}\n#{@scribe.config[:remote_name]}\nyet_another_#{@scribe.config[:remote_name]}" }
240
+ @scribe.should_receive(:shell_out!).with(@remote_command,
241
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
242
+ @scribe.remote_configured?.should be(true)
243
+ end
244
+ end
245
+
246
+ describe "#fetch" do
247
+ it "fetches the changes from the remote repository" do
248
+ fetch_command = "git fetch #{@scribe.config[:remote_name]}"
249
+ @scribe.should_receive(:shell_out!).with(fetch_command,
250
+ :cwd => @scribe.config[:chronicle_path])
251
+ @scribe.fetch
252
+ end
253
+ end
254
+
255
+
256
+ describe "#pull" do
257
+ before(:each) do
258
+ @command_response = double('shell_out')
259
+ @command_response.stub(:exitstatus) { 0 }
260
+ end
261
+
262
+ describe "when a remote branch already exists" do
263
+ it "pulls from the remote repository" do
264
+ @command_response.stub(:stdout) { "#{@scribe.config[:branch]}\nremotes/#{@scribe.config[:remote_name]}/#{@scribe.config[:branch]}" }
265
+ check_remote_branch_command = "git branch -a"
266
+ @scribe.should_receive(:shell_out!).with(check_remote_branch_command,
267
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
268
+ pull_command = "git pull #{@scribe.config[:remote_name]} #{@scribe.config[:branch]}"
269
+ @scribe.should_receive(:shell_out!).with(pull_command,
270
+ :cwd => @scribe.config[:chronicle_path])
271
+ @scribe.pull
272
+ end
273
+ end
274
+
275
+ describe "when a remote branch doesn't already exist" do
276
+ it "doesn't pull'" do
277
+ @command_response.stub(:stdout) { "#{@scribe.config[:branch]}2\nremotes/#{@scribe.config[:remote_name]}/#{@scribe.config[:branch]}2" }
278
+ check_remote_branch_command = "git branch -a"
279
+ @scribe.should_receive(:shell_out!).with(check_remote_branch_command,
280
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
281
+ pull_command = "git pull #{@scribe.config[:remote_name]} #{@scribe.config[:branch]}"
282
+ @scribe.should_not_receive(:shell_out!).with(pull_command,
283
+ :cwd => @scribe.config[:chronicle_path])
284
+ @scribe.pull
285
+ end
286
+ end
287
+
288
+ describe "when the repository is empty" do
289
+ it "doesn't pull'" do
290
+ @command_response.stub(:stdout) { "" }
291
+ check_remote_branch_command = "git branch -a"
292
+ @scribe.should_receive(:shell_out!).with(check_remote_branch_command,
293
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
294
+ pull_command = "git pull #{@scribe.config[:remote_name]} #{@scribe.config[:branch]}"
295
+ @scribe.should_not_receive(:shell_out!).with(pull_command,
296
+ :cwd => @scribe.config[:chronicle_path])
297
+ @scribe.pull
298
+ end
299
+
300
+ end
301
+ end
302
+
303
+ describe "#commit" do
304
+ before(:each) do
305
+ @command_response = double('shell_out')
306
+ @command_response.stub(:exitstatus) { 0 }
307
+ @command_response.stub(:stdout) { "" }
308
+ @scribe.config[:commit_message] = "Commit message at %TIME%"
309
+ end
310
+
311
+ it "adds all files prior to commit" do
312
+ expected_command = "git add ."
313
+ @scribe.should_receive(:shell_out!).with(expected_command,
314
+ :cwd => @scribe.config[:chronicle_path]).and_return(@command_response)
315
+ pull_command = "git pull remote_name branch_name"
316
+ @scribe.stub(:shell_out!)
317
+ @scribe.commit
318
+ end
319
+
320
+ it "commits all changes" do
321
+ expected_command = "git commit -m \"#{@scribe.config[:commit_message].gsub(/%TIME%/, Time.now.to_s)}\""
322
+ @scribe.stub(:shell_out!)
323
+ @scribe.should_receive(:shell_out!).with(expected_command,
324
+ :cwd => @scribe.config[:chronicle_path],
325
+ :returns => [0, 1]).and_return(@command_response)
326
+ @scribe.commit
327
+ end
328
+ end
329
+
330
+ describe "#push" do
331
+ it "pushes to the remote repository" do
332
+ push_command = "git push #{@scribe.config[:remote_name]} #{@scribe.config[:branch]}"
333
+ @scribe.should_receive(:shell_out!).with(push_command,
334
+ :cwd => @scribe.config[:chronicle_path])
335
+ @scribe.push
336
+ end
337
+ end
338
+
339
+ describe "#fetch_configs" do
340
+ before(:each) do
341
+ @scribe.stub(:fetch_environments)
342
+ @scribe.stub(:fetch_roles)
343
+ @scribe.stub(:fetch_nodes)
344
+ end
345
+
346
+ it "fetches environment configs" do
347
+ @scribe.should_receive(:fetch_environments)
348
+ @scribe.fetch_configs
349
+ end
350
+
351
+ it "fetches roles configs" do
352
+ @scribe.should_receive(:fetch_roles)
353
+ @scribe.fetch_configs
354
+ end
355
+
356
+ it "fetches nodes configs" do
357
+ @scribe.should_receive(:fetch_nodes)
358
+ @scribe.fetch_configs
359
+ end
360
+ end
361
+
362
+ describe "#fetch_environments" do
363
+ before(:each) do
364
+ @environment1 = { :test1 => :value1 }
365
+ @environment1.stub(:name) { "env_name1" }
366
+ @environment2 = { :test2 => :value2 }
367
+ @environment2.stub(:name) { "env_name2" }
368
+ Chef::Environment.stub(:list) { { @environment1.name => @environment1, @environment2.name => @environment2 } }
369
+ end
370
+
371
+ it "saves each env to a file" do
372
+ @scribe.should_receive(:save_to_file).with("environments", @environment1.name, @environment1)
373
+ @scribe.should_receive(:save_to_file).with("environments", @environment2.name, @environment2)
374
+ @scribe.fetch_environments
375
+ end
376
+ end
377
+
378
+ describe "#fetch_roles" do
379
+ before(:each) do
380
+ @role1 = { :test1 => :value1 }
381
+ @role1.stub(:name) { "role_name1" }
382
+ @role2 = { :test2 => :value2 }
383
+ @role2.stub(:name) { "role_name2" }
384
+ Chef::Role.stub(:list) { { @role1.name => @role1, @role2.name => @role2 } }
385
+ end
386
+
387
+ it "saves each role to a file" do
388
+ @scribe.should_receive(:save_to_file).with("roles", @role1.name, @role1)
389
+ @scribe.should_receive(:save_to_file).with("roles", @role2.name, @role2)
390
+ @scribe.fetch_roles
391
+ end
392
+ end
393
+
394
+ describe "#fetch_nodes" do
395
+ before(:each) do
396
+ @node1 = { :test1 => :value1 }
397
+ @node1.stub(:name) { "node_name1" }
398
+ @node1.stub(:chef_environment) { "chef_environment1" }
399
+ @node1.stub(:normal_attrs) { { :attr1 => "val1" } }
400
+ @node1.stub(:run_list) { ["cookbook1", "cookbook2"] }
401
+ @serialized_node1 = {"name" => @node1.name, "env" => @node1.chef_environment, "attribiutes" => @node1.normal_attrs, "run_list" => @node1.run_list}
402
+ @node2 = { :test2 => :value2 }
403
+ @node2.stub(:name) { "node_name2" }
404
+ @node2.stub(:chef_environment) { "chef_environment2" }
405
+ @node2.stub(:normal_attrs) { { :attrA => "valA" } }
406
+ @node2.stub(:run_list) { ["cookbookA", "cookbookB"] }
407
+ @serialized_node2 = {"name" => @node2.name, "env" => @node2.chef_environment, "attribiutes" => @node2.normal_attrs, "run_list" => @node2.run_list}
408
+ Chef::Node.stub(:list) { { @node1.name => @node1, @node2.name => @node2 } }
409
+ end
410
+
411
+ it "saves each node to a file" do
412
+ @scribe.should_receive(:save_to_file).with("nodes", @node1.name, @serialized_node1)
413
+ @scribe.should_receive(:save_to_file).with("nodes", @node2.name, @serialized_node2)
414
+ @scribe.fetch_nodes
415
+ end
416
+ end
417
+
418
+ describe "#save_to_file" do
419
+ before(:each) do
420
+ @f1 = double()
421
+ @f1.stub(:write)
422
+ @data = { :test_key2 => "test_value2", :test_key2 => "test_value2"}
423
+ end
424
+
425
+ it "saves deeply sorted data into a specific file in a specific directory" do
426
+ File.should_receive(:open).with(File.join(@scribe.config[:chronicle_path], "dir", "name.json"), "w").and_yield(@f1)
427
+ @scribe.should_receive(:deep_sort).with(@data).and_return({:sorted => "data"})
428
+ @f1.should_receive(:write).with(JSON.pretty_generate({:sorted => "data"}))
429
+ @scribe.save_to_file "dir", "name", @data
430
+ end
431
+ end
432
+
433
+
434
+ describe "#deep_sort" do
435
+ describe "when it gets a hash as a parameter" do
436
+ it "sorts the hash" do
437
+ sorted_hash = @scribe.deep_sort({:c => 3, :a => 1, :x => 0, :d => -2})
438
+ sorted_values = [[:a, 1], [:c,3], [:d,-2], [:x,0]]
439
+ i = 0
440
+ sorted_hash.each do |key, value|
441
+ key.should eql(sorted_values[i][0])
442
+ value.should eql(sorted_values[i][1])
443
+ i +=1
444
+ end
445
+ end
446
+
447
+ it "calls itself recursively with each value" do
448
+ hash_to_sort = {:g => 3, :b => 1, :z => 0, :h => -2}
449
+ @scribe.should_receive(:deep_sort).with(hash_to_sort).and_call_original
450
+ hash_to_sort.values.each {|value| @scribe.should_receive(:deep_sort).with(value)}
451
+ @scribe.deep_sort(hash_to_sort)
452
+ end
453
+
454
+ it "returns a deep sorted hash" do
455
+ hash_to_sort = {"zz" => { "z" => 0, "h" => -2}}
456
+ @scribe.deep_sort(hash_to_sort).should eql({"zz" => { "h" => -2, "z" => 0}})
457
+ end
458
+ end
459
+
460
+ describe "when it gets a simple array as a parameter" do
461
+ it "doesn't sort the array" do
462
+ array = [3, 1, 0, -2]
463
+ sorted_hash = @scribe.deep_sort(array)
464
+ sorted_hash.each_with_index do |value, index|
465
+ value.should eql(array[index])
466
+ end
467
+ end
468
+
469
+ it "calls itself recursively with each value" do
470
+ array = [3, 1, 0, -2]
471
+ @scribe.should_receive(:deep_sort).with(array).and_call_original
472
+ array.each {|value| @scribe.should_receive(:deep_sort).with(value)}
473
+ @scribe.deep_sort(array)
474
+ end
475
+
476
+ it "returns a deep sorted array" do
477
+ hash_to_sort = [{ "u" => 0, "h" => -2 }, { "u" => "test", "d" => -100 }]
478
+ @scribe.deep_sort(hash_to_sort).should eql([{ "h" => -2, "u" => 0 }, { "d" => -100, "u" => "test" }])
479
+ end
480
+ end
481
+
482
+ describe "when it gets something that's not a hash or an array as a prameter" do
483
+ it "returns the input param" do
484
+ str = "test"
485
+ @scribe.deep_sort(str).should equal(str)
486
+ @scribe.deep_sort(1).should equal(1)
487
+ @scribe.deep_sort(true).should equal(true)
488
+ @scribe.deep_sort(nil).should eql(nil)
489
+ end
490
+ end
491
+ end
492
+ end