kitchen-scribe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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