stackfu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/CHANGELOG +24 -0
  2. data/Manifest +118 -0
  3. data/README +0 -0
  4. data/README.md +218 -0
  5. data/Rakefile +73 -0
  6. data/bin/stackfu +10 -0
  7. data/lib/stackfu/api_hooks.rb +17 -0
  8. data/lib/stackfu/app.rb +69 -0
  9. data/lib/stackfu/commands/command.rb +127 -0
  10. data/lib/stackfu/commands/config_command.rb +43 -0
  11. data/lib/stackfu/commands/deploy_command.rb +144 -0
  12. data/lib/stackfu/commands/dump_command.rb +101 -0
  13. data/lib/stackfu/commands/generate_command.rb +107 -0
  14. data/lib/stackfu/commands/help_command.rb +28 -0
  15. data/lib/stackfu/commands/list_command.rb +26 -0
  16. data/lib/stackfu/commands/publish_command.rb +108 -0
  17. data/lib/stackfu/commands/server_command.rb +124 -0
  18. data/lib/stackfu/helpers/providers_credentials.rb +82 -0
  19. data/lib/stackfu/helpers/rendering.rb +201 -0
  20. data/lib/stackfu/operating_systems.rb +30 -0
  21. data/lib/stackfu.rb +88 -0
  22. data/stackfu-installer/config/01-controls.yml +17 -0
  23. data/stackfu-installer/config/02-requirements.yml +3 -0
  24. data/stackfu-installer/config/03-scripts.yml +16 -0
  25. data/stackfu-installer/config/04-validations.yml +3 -0
  26. data/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
  27. data/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
  28. data/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
  29. data/stackfu-installer/script/redis_installation.sh.erb +33 -0
  30. data/stackfu-installer/script/resque_installation.sh.erb +7 -0
  31. data/stackfu-installer/script/ruby_environment.sh.erb +20 -0
  32. data/stackfu-installer/script/stackfu.sh.erb +114 -0
  33. data/stackfu-installer/stack.yml +5 -0
  34. data/stackfu.gemspec +60 -0
  35. data/templates/01-controls.yml.erb +31 -0
  36. data/templates/02-requirements.yml.erb +26 -0
  37. data/templates/03-scripts.yml.erb +34 -0
  38. data/templates/04-validations.yml.erb +17 -0
  39. data/templates/script.sh.erb +7 -0
  40. data/templates/stack.yml.erb +17 -0
  41. data/test/fixtures/add_server_error +7 -0
  42. data/test/fixtures/deployment_add +7 -0
  43. data/test/fixtures/deployment_add_error +8 -0
  44. data/test/fixtures/deployments +7 -0
  45. data/test/fixtures/logs +7 -0
  46. data/test/fixtures/logs_partial +7 -0
  47. data/test/fixtures/plugin_add +6 -0
  48. data/test/fixtures/plugin_add_error +8 -0
  49. data/test/fixtures/plugin_deployment_add +7 -0
  50. data/test/fixtures/plugin_not_found +7 -0
  51. data/test/fixtures/plugin_unauthorized +8 -0
  52. data/test/fixtures/plugins +7 -0
  53. data/test/fixtures/plugins_by_name +7 -0
  54. data/test/fixtures/plugins_by_name_other +7 -0
  55. data/test/fixtures/plugins_empty +7 -0
  56. data/test/fixtures/plugins_multiple +7 -0
  57. data/test/fixtures/providers +7 -0
  58. data/test/fixtures/providers_servers +7 -0
  59. data/test/fixtures/server_add +6 -0
  60. data/test/fixtures/server_add_dupe +7 -0
  61. data/test/fixtures/server_add_error +7 -0
  62. data/test/fixtures/server_delete +7 -0
  63. data/test/fixtures/server_delete_error +7 -0
  64. data/test/fixtures/servers +7 -0
  65. data/test/fixtures/servers_by_name +7 -0
  66. data/test/fixtures/servers_empty +8 -0
  67. data/test/fixtures/servers_not_found +26 -0
  68. data/test/fixtures/servers_unauthorized +8 -0
  69. data/test/fixtures/servers_webbynode +7 -0
  70. data/test/fixtures/stack/stackfu-installer/config/01-controls.yml +22 -0
  71. data/test/fixtures/stack/stackfu-installer/config/02-requirements.yml +1 -0
  72. data/test/fixtures/stack/stackfu-installer/config/03-scripts.yml +23 -0
  73. data/test/fixtures/stack/stackfu-installer/config/04-validations.yml +1 -0
  74. data/test/fixtures/stack/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
  75. data/test/fixtures/stack/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
  76. data/test/fixtures/stack/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
  77. data/test/fixtures/stack/stackfu-installer/script/redis_installation.sh.erb +33 -0
  78. data/test/fixtures/stack/stackfu-installer/script/resque_installation.sh.erb +7 -0
  79. data/test/fixtures/stack/stackfu-installer/script/ruby_environment.sh.erb +20 -0
  80. data/test/fixtures/stack/stackfu-installer/script/stackfu.sh.erb +76 -0
  81. data/test/fixtures/stack/stackfu-installer/stack.yml +5 -0
  82. data/test/fixtures/stack_add +6 -0
  83. data/test/fixtures/stack_add_error +8 -0
  84. data/test/fixtures/stack_add_error_dupe +8 -0
  85. data/test/fixtures/stack_adds_by_name +7 -0
  86. data/test/fixtures/stack_delete_not_found +7 -0
  87. data/test/fixtures/stacks +7 -0
  88. data/test/fixtures/stacks_by_name +7 -0
  89. data/test/fixtures/stacks_by_name_other +7 -0
  90. data/test/fixtures/stacks_empty +7 -0
  91. data/test/fixtures/stacks_multiple +7 -0
  92. data/test/fixtures/stacks_not_found +7 -0
  93. data/test/fixtures/stacks_realworld +7 -0
  94. data/test/fixtures/stacks_stackfu-installer +7 -0
  95. data/test/fixtures/stacks_unauthorized +8 -0
  96. data/test/fixtures/stacks_with_controls +0 -0
  97. data/test/fixtures/users +7 -0
  98. data/test/fixtures/users_no_credentials +7 -0
  99. data/test/fixtures/users_update +6 -0
  100. data/test/stack.yml +26 -0
  101. data/test/support/custom_matchers.rb +69 -0
  102. data/test/support/fixtures.rb +98 -0
  103. data/test/support/io_stub.rb +10 -0
  104. data/test/support/web_fixtures.rb +91 -0
  105. data/test/test_helper.rb +186 -0
  106. data/test/unit/commands/test_command.rb +112 -0
  107. data/test/unit/commands/test_config_command.rb +92 -0
  108. data/test/unit/commands/test_deploy_command.rb +303 -0
  109. data/test/unit/commands/test_dump_command.rb +155 -0
  110. data/test/unit/commands/test_generate_command.rb +112 -0
  111. data/test/unit/commands/test_help_command.rb +33 -0
  112. data/test/unit/commands/test_list_command.rb +63 -0
  113. data/test/unit/commands/test_publish_command.rb +265 -0
  114. data/test/unit/commands/test_server_command.rb +259 -0
  115. data/test/unit/helpers/test_rendering.rb +141 -0
  116. data/test/unit/test_array.rb +26 -0
  117. data/test/unit/test_provider.rb +14 -0
  118. data/test/unit/test_stackfu.rb +27 -0
  119. metadata +311 -0
@@ -0,0 +1,6 @@
1
+ HTTP/1.1 200 OK
2
+ Connection: close
3
+ Date: Sat, 14 Nov 2009 17:34:12 GMT
4
+ Content-Type: application/json;charset=utf-8
5
+ Content-Length: 0
6
+
data/test/stack.yml ADDED
@@ -0,0 +1,26 @@
1
+ ---
2
+ type:
3
+ name: "test"
4
+ #
5
+ # TODO: Before submitting this stack, you have to specify
6
+ # the attributes below.
7
+ #
8
+ # Available operating systems
9
+ #
10
+ # For OS Use
11
+ # -------------------- --------------------
12
+ # Debian 5.0 debian_50
13
+ # Gentoo 2008 gentoo_2008
14
+ # Ubuntu 8.10 ubuntu_810
15
+ # Fedora 10 fedora_10
16
+ # Arch 2009 arch_2009
17
+ # Ubuntu 9.04 ubuntu_904
18
+ # Centos 5.2 centos_52
19
+ # ArchLinux 2009 arch_2009
20
+ # Ubuntu 8.04 ubuntu_804
21
+ # Centos 5.3 centos_53
22
+ #
23
+ # operating_system:
24
+ # description: "Enter a description for this stack here"
25
+ # tags: [add, some, tags]
26
+ #
@@ -0,0 +1,69 @@
1
+ module CustomMatchers
2
+ custom_matcher :be_nil do |receiver, matcher, args|
3
+ matcher.positive_failure_message = "Expected #{receiver} to be nil but it wasn't"
4
+ matcher.negative_failure_message = "Expected #{receiver} not to be nil but it was"
5
+ receiver.nil?
6
+ end
7
+
8
+ custom_matcher :be_blank do |receiver, matcher, args|
9
+ matcher.positive_failure_message = "Expected #{receiver} to be blank but it wasn't"
10
+ matcher.negative_failure_message = "Expected #{receiver} not to be blank but it was"
11
+ receiver.blank?
12
+ end
13
+
14
+ custom_matcher :be_true do |receiver, matcher, args|
15
+ matcher.positive_failure_message = "Expected #{receiver} to be true but it wasn't"
16
+ matcher.negative_failure_message = "Expected #{receiver} not to be true but it was"
17
+ receiver.eql?(true)
18
+ end
19
+
20
+ custom_matcher :be_false do |receiver, matcher, args|
21
+ matcher.positive_failure_message = "Expected #{receiver} to be false but it wasn't"
22
+ matcher.negative_failure_message = "Expected #{receiver} not to be false but it was"
23
+ receiver.eql?(false)
24
+ end
25
+
26
+ custom_matcher :be_valid do |receiver, matcher, args|
27
+ matcher.positive_failure_message = "Expected to be valid but it was invalid #{receiver.errors.inspect}"
28
+ matcher.negative_failure_message = "Expected to be invalid but it was valid #{receiver.errors.inspect}"
29
+ receiver.valid?
30
+ end
31
+
32
+ custom_matcher :be_of_type do |receiver, matcher, args|
33
+ klass = args[0]
34
+ matcher.positive_failure_message = "Expected #{receiver} to be of type #{klass} but it wasn't"
35
+ matcher.negative_failure_message = "Expected #{receiver} to not be of type #{klass} but it was"
36
+ receiver.kind_of?(klass)
37
+ end
38
+
39
+ custom_matcher :start_with do |receiver, matcher, args|
40
+ s = args[0]
41
+ matcher.positive_failure_message = "Expected #{receiver} to start with '#{s}' but it didn't"
42
+ matcher.negative_failure_message = "Expected #{receiver} not to start with '#{s}' but it did"
43
+ true if receiver =~ /^#{s}/
44
+ end
45
+
46
+ custom_matcher :have_error_on do |receiver, matcher, args|
47
+ receiver.valid?
48
+ attribute = args[0]
49
+ expected_message = args[1]
50
+
51
+ if expected_message.nil?
52
+ matcher.positive_failure_message = "#{receiver} had no errors on #{attribute}"
53
+ matcher.negative_failure_message = "#{receiver} had errors on #{attribute} #{receiver.errors.inspect}"
54
+ !receiver.errors.on(attribute).blank?
55
+ else
56
+ actual = receiver.errors.on(attribute)
57
+ matcher.positive_failure_message = %Q(Expected error on #{attribute} to be "#{expected_message}" but was "#{actual}")
58
+ matcher.negative_failure_message = %Q(Expected error on #{attribute} not to be "#{expected_message}" but was "#{actual}")
59
+ actual == expected_message
60
+ end
61
+ end
62
+
63
+ custom_matcher :have_index do |receiver, matcher, args|
64
+ index_name = args[0]
65
+ matcher.positive_failure_message = "#{receiver} does not have index named #{index_name}, but should"
66
+ matcher.negative_failure_message = "#{receiver} does have index named #{index_name}, but should not"
67
+ !receiver.collection.index_information.detect { |index| index[0] == index_name }.nil?
68
+ end
69
+ end
@@ -0,0 +1,98 @@
1
+ module Fixtures
2
+ ApiUrlPrefix = StackFu::API.gsub(/api/, "flipper:abc123@api")
3
+
4
+ def with_logs(id, kind=nil)
5
+ register :get, :path => "deployments/#{id}/logs", :kind => kind
6
+ end
7
+
8
+ def with_providers(kind=nil)
9
+ register :get, :path => "providers", :kind => kind
10
+ end
11
+
12
+ def with_provider(id, kind=nil)
13
+ register :get, :path => "providers/#{id}/#{kind}", :fixture => "providers", :kind => kind
14
+ end
15
+
16
+ def with_stacks(kind=nil, params=nil)
17
+ register :get, :path => "stacks", :kind => kind, :params => params
18
+ end
19
+
20
+ def with_stack_add(kind=nil)
21
+ register :post, :path => "stacks", :fixture => "stack_add", :kind => kind
22
+ end
23
+
24
+ def with_stack_delete(id=nil, fixture="stack_add", kind=nil)
25
+ register :delete, :path => "stacks/#{id}", :fixture => fixture, :kind => kind
26
+ end
27
+
28
+ def with_plugins(kind=nil, params=nil)
29
+ register :get, :path => "plugins", :kind => kind, :params => params
30
+ end
31
+
32
+ def with_plugin_add(kind=nil)
33
+ register :post, :path => "plugins", :fixture => "plugin_add", :kind => kind
34
+ end
35
+
36
+ def with_plugin_delete(id=nil, fixture="plugin_add", kind=nil)
37
+ register :delete, :path => "plugins/#{id}", :fixture => fixture, :kind => kind
38
+ end
39
+
40
+ def with_server_list(kind=nil, params=nil)
41
+ register :get, :path => "servers", :kind => kind, :params => params
42
+ end
43
+
44
+ def with_server_add(kind=nil)
45
+ register :post, :path => "servers", :fixture => "server_add", :kind => kind
46
+ end
47
+
48
+ def with_server_delete(kind=nil)
49
+ register :delete, :path => "servers/4afe06b9e1054e1e00000002", :fixture => "server_add", :kind => kind
50
+ end
51
+
52
+ def with_user(id, kind=nil)
53
+ register :get, :path => "users/#{id}/#{kind}", :fixture => "users", :kind => kind
54
+ end
55
+
56
+ def with_users(kind=nil)
57
+ register :get, :path => "users", :kind => kind
58
+ end
59
+
60
+ def with_users_add(kind=nil)
61
+ register :post, :path => "users", :fixture => "users_update", :kind => kind
62
+ end
63
+
64
+ def with_users_update(id=nil, kind=nil)
65
+ register :put, :path => "users#{id ? "/#{id}" : ""}", :fixture => "users_update", :kind => kind
66
+ end
67
+
68
+ def with_new_deployment(kind=nil)
69
+ register :post, :path => "deployments", :fixture => "deployment_add", :kind => kind
70
+ end
71
+
72
+ def with_new_plugin_deployment(kind=nil)
73
+ register :post, :path => "deployments", :fixture => "plugin_deployment_add", :kind => kind
74
+ end
75
+
76
+ private
77
+
78
+ def register(method, options) # path, kind, fixture=path)
79
+ kind = options[:kind] ? "_#{options[:kind]}" : ""
80
+ path = options[:path] or raise "path is mandatory"
81
+ fixture = options[:fixture] || options[:path]
82
+ params = options[:params] ? "?#{options[:params]}" : ""
83
+
84
+ # d "Registering: #{method} #{ApiUrlPrefix}/#{path}.json#{params} => #{fixture}#{kind}"
85
+ FakeWeb.register_uri(method, "#{ApiUrlPrefix}/#{path}.json#{params}",
86
+ :response => fixture("#{fixture}#{kind}"))
87
+ end
88
+
89
+ def fixture(*path)
90
+ file_name = File.join(File.dirname(__FILE__), "../fixtures", path)
91
+ raise "Fixture #{file_name} was not found" unless File.exists?(file_name)
92
+ file_name
93
+ end
94
+
95
+ def fixture_contents(*path)
96
+ File.open(File.join(File.dirname(__FILE__), "../fixtures", path), "r").readlines.join("")
97
+ end
98
+ end
@@ -0,0 +1,10 @@
1
+ module IoStub
2
+ def stdout
3
+ $stdout.rewind
4
+ $stdout.read
5
+ end
6
+
7
+ def debug(s)
8
+ @orig_stdout.puts s
9
+ end
10
+ end
@@ -0,0 +1,91 @@
1
+ module WebFixtures
2
+ def stub_file(*path)
3
+ File.join(File.dirname(__FILE__), "../stubs", path)
4
+ end
5
+
6
+ def stub_file_contents(*path)
7
+ File.open(File.join(File.dirname(__FILE__), "../stubs", path), "r").readlines.join("")
8
+ end
9
+
10
+ def fake_webby_os(os)
11
+ yaml_api = "https://manager.webbynode.com/api/yaml"
12
+
13
+ response = stub_file_contents("webbynode", "fcoury@me.com", "webbies")
14
+ response.gsub! ":os_template: 32", ":os_template: #{os}"
15
+
16
+ FakeWeb.clean_registry
17
+ FakeWeb.register_uri(:post, "#{yaml_api}/webbies", :response => response)
18
+ end
19
+
20
+ def fake_webbynode
21
+ FakeWeb.clean_registry
22
+ yaml_api = "https://manager.webbynode.com/api/yaml"
23
+
24
+ users = {
25
+ "fcoury@me.com" => "1da75b299084548ccd84990e463d4266e96abc5a",
26
+ "ctab@me.com" => "2da127cbffd4266e96abc5a8bbc671e1c2bb3bac",
27
+ }
28
+
29
+ fakes = {
30
+ "webbies" => "webbies",
31
+ "client" => "client"
32
+ }
33
+
34
+ users.each_pair do |email, token|
35
+ fakes.each_pair do |key, value|
36
+ FakeWeb.register_uri(:post, "#{yaml_api}/#{key}", :email => email, :response => stub_file("webbynode", email, value))
37
+ end
38
+ end
39
+ end
40
+
41
+ SlicehostUsers = {
42
+ "felipe.coury@gmail.com" => "6ed05e7521dfb6a19c98a84f2d0a28fdffc1bf00e050fe7114a66d3049e9715e"
43
+ }
44
+
45
+ def fake_slicehost
46
+ FakeWeb.clean_registry
47
+
48
+ SlicehostUsers.each_pair do |email, token|
49
+ slicehost_register(:get, "/slices.xml", "slices")
50
+ end
51
+ end
52
+
53
+ def fake_slicehost_no_slice
54
+ FakeWeb.clean_registry
55
+
56
+ SlicehostUsers.each_pair do |email, token|
57
+ slicehost_register(:get, "/slices.xml", "slices_none")
58
+ end
59
+ end
60
+
61
+ def fake_slicehost_os(os)
62
+ user = SlicehostUsers.keys.first
63
+ token = SlicehostUsers.values.first
64
+
65
+ xml_api = "https://#{token}@api.slicehost.com"
66
+
67
+ response = stub_file_contents("slicehost", user, "slices")
68
+ response.gsub! "Content-Length: 554", "Content-Length: 553" if os.to_s.size < 2
69
+ response.gsub! '<image-id type="integer">10</image-id>', "<image-id type=\"integer\">#{os}</image-id>"
70
+
71
+ FakeWeb.clean_registry
72
+ FakeWeb.register_uri(:get, "#{xml_api}/slices.xml", :response => response)
73
+ end
74
+
75
+ def fake_slice_creation(success=true)
76
+ slicehost_register :post, "/slices.xml", "create_slice"
77
+ end
78
+
79
+ def fake_slice_deletion(slice_id, error="")
80
+ slicehost_register :delete, "/slices/#{slice_id}.xml", "delete_slice#{error.present? ? "_#{error}" : ""}"
81
+ end
82
+
83
+ def slicehost_register(method, uri, file, user=SlicehostUsers.keys.first)
84
+ token = SlicehostUsers[user]
85
+
86
+ file = stub_file("slicehost", user, file)
87
+ prefix = "https://#{token}@api.slicehost.com"
88
+
89
+ FakeWeb.register_uri(method, "#{prefix}#{uri}", :response => file)
90
+ end
91
+ end
@@ -0,0 +1,186 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/stackfu')
2
+
3
+ gem 'fcoury-matchy', '0.4.0'
4
+ gem 'shoulda', '2.10.2'
5
+ gem 'timecop', '0.3.1'
6
+ gem 'mocha', '>=0.9.4'
7
+ gem 'fakeweb', '>=1.2.7'
8
+ gem 'phocus', '>=1.1'
9
+ gem 'uuid', '>=2.0.2'
10
+
11
+ require 'matchy'
12
+ require 'shoulda'
13
+ require 'fakeweb'
14
+ require 'phocus'
15
+ require 'mocha'
16
+ require 'uuid'
17
+ require 'pp'
18
+
19
+ require 'support/custom_matchers'
20
+ require 'support/io_stub'
21
+ require 'support/fixtures'
22
+
23
+ HighLine.track_eof = false
24
+ FakeWeb.allow_net_connect = false
25
+ $testing = true
26
+
27
+ class Test::Unit::TestCase
28
+ include CustomMatchers
29
+ include IoStub
30
+ include Fixtures
31
+ include StackFu
32
+
33
+ def command(cmd, settings_present=true)
34
+ cmd ||= ""
35
+ App.expects(:settings?).returns(settings_present)
36
+ if settings_present
37
+ App.any_instance.expects(:load_config).returns({ :login => "flipper", :token => "abc123" })
38
+ end
39
+ App.new(cmd.split(" ")).start
40
+ end
41
+
42
+ def setup
43
+ FakeWeb.clean_registry
44
+ @orig_stdout = $stdout
45
+ $stdout = StringIO.new
46
+ $actions = []
47
+ $asked = []
48
+ end
49
+
50
+ def when_asked_to_choose(what, options)
51
+ $actions << { :type => "choose", :action => options.merge(:prompt => what) }
52
+ end
53
+
54
+ def when_asked(what, options)
55
+ $actions << { :type => "ask", :action => [what, options[:answer]] }
56
+ end
57
+
58
+ def agree_with(what)
59
+ $actions << { :type => "agree", :action => what }
60
+ end
61
+
62
+ def disagree_of(what)
63
+ $actions << { :type => "disagree", :action => what }
64
+ end
65
+
66
+ def teardown
67
+ $stdout = @orig_stdout
68
+ asked = $asked.map { |s| " - #{s}" }.join("\n")
69
+ unless $actions.empty?
70
+ questions = $actions.map do |q|
71
+ info = case q[:type]
72
+ when "ask"
73
+ q[:action].first
74
+
75
+ when "choose"
76
+ txt = "'#{q[:action][:prompt]}'"
77
+ txt << " with options '#{q[:action][:with_options].join(", ")}'" if q[:action][:with_options]
78
+ else
79
+ q[:action]
80
+ end
81
+
82
+ " - #{q[:type]} #{info}"
83
+ end.join("\n")
84
+ fail "Expected questions not asked:\n#{questions}\nQuestions responded:\n#{asked}\n"
85
+ end
86
+ end
87
+ end
88
+
89
+ module StackFu
90
+ class ConfigCommand
91
+ def save_config(login, token)
92
+ end
93
+ end
94
+ end
95
+
96
+ class Array
97
+ def delete_first(value=nil)
98
+ result = nil
99
+ self.each_index do |i|
100
+ if block_given? and yield(self[i])
101
+ result = self.delete_at(i)
102
+ break
103
+ end
104
+ if value and self[i] == value
105
+ result = self.delete_at(i)
106
+ break
107
+ end
108
+ end
109
+
110
+ return result unless result and result.empty?
111
+ end
112
+ end
113
+
114
+ module Kernel
115
+ def choose(*items, &details)
116
+ if (action = $actions.first)
117
+ if action[:type] == "choose"
118
+ item = OpenStruct.new
119
+ class << item
120
+ def choice(*args)
121
+ (self.choices||=[]) << args
122
+ end
123
+ end
124
+
125
+ details.call item
126
+
127
+ exp_action = $actions.shift[:action]
128
+ item.prompt.should == exp_action[:prompt]
129
+
130
+ exp_action[:with_options].try(:each) do |opt|
131
+ item.choices.flatten.include?(opt).should == true
132
+ end
133
+
134
+ if (answer = exp_action[:answer]).is_a?(Numeric)
135
+ item.choices[answer][0]
136
+ else
137
+ answer
138
+ end
139
+ end
140
+ else
141
+ fail "Unexpected choose"
142
+ end
143
+ end
144
+
145
+ def ask(question, answer_type = String, &details)
146
+ if (action = $actions.first)
147
+ matches = if (expected = action[:action].first).is_a?(Regexp)
148
+ question =~ expected
149
+ else
150
+ question == expected
151
+ end
152
+
153
+ if action[:type] == "ask" and matches
154
+ $asked << "responded to ask #{action[:action].first} with #{action[:action].last}"
155
+ exp_action = $actions.shift
156
+ result = exp_action[:action].last
157
+ return result
158
+ end
159
+
160
+ fail "Expected to #{action[:type]} #{action[:action].inspect} but asked #{question.inspect}"
161
+ else
162
+ fail "Unexpected ask #{question.inspect}"
163
+ end
164
+ end
165
+
166
+ def agree(yes_or_no_question, character = nil)
167
+ action = $actions.first
168
+ if action and action[:action] == yes_or_no_question
169
+ if action[:type] == "agree" or action[:type] == "disagree"
170
+ $asked << "#{action[:type]}d to #{action[:action]}"
171
+ result = $actions.shift[:type] == "agree"
172
+ # d "#{action[:type]} #{yes_or_no_question} => #{result}"
173
+ return result
174
+ end
175
+ end
176
+
177
+ if action
178
+ fail "Expected to #{action[:type]} #{action[:action].inspect} but asked to agree with #{yes_or_no_question.inspect}"
179
+ else
180
+ fail "Unexpected agreement #{yes_or_no_question.inspect}"
181
+ end
182
+ end
183
+ end
184
+
185
+ def d(x); $stderr.puts x; end
186
+ def ppd(x); $stderr.puts x.pretty_inspect; end
@@ -0,0 +1,112 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper.rb'
2
+
3
+ class SampleCmd < StackFu::Command
4
+ end
5
+
6
+ class TestCommand < Test::Unit::TestCase
7
+ should "return true to params? if params given" do
8
+ SampleCmd.new(["hello", "world"]).params?.should be_true
9
+ end
10
+
11
+ should "return false to params? if params given" do
12
+ SampleCmd.new(["--hello", "--world"]).params?.should be_false
13
+ end
14
+
15
+ should "allow command with no subcommand" do
16
+ cmd = SampleCmd.new(["--force"])
17
+ cmd.subcommand.should == :default
18
+ cmd.parameters.should == []
19
+ cmd.options.should == { :force => true }
20
+
21
+ cmd = SampleCmd.new(["mine", "yellow", "--azul=b"])
22
+ cmd.subcommand.should == :mine
23
+ cmd.parameters.should == ["yellow"]
24
+ cmd.options.should == { :azul => "b" }
25
+ end
26
+
27
+ should "parse as parameter if = is present" do
28
+ cmd = SampleCmd.new(["--force", "--ssh_passphrase="])
29
+ cmd.options.should == { :force => true, :ssh_passphrase => "" }
30
+ end
31
+
32
+ should "parse subcommand and options" do
33
+ cmd = SampleCmd.new(["copy", "--force", "/var/opt/music/mine.mp3", "/home/fcoury"])
34
+ cmd.subcommand.should == :copy
35
+ cmd.parameters.should == ["/var/opt/music/mine.mp3", "/home/fcoury"]
36
+ cmd.options.should == { :force => true }
37
+
38
+ cmd = SampleCmd.new(["move", "/var/log/1.log", "--mode=FAILSAFE", "/var/log/one.log"])
39
+ cmd.subcommand.should == :move
40
+ cmd.parameters.should == ["/var/log/1.log", "/var/log/one.log"]
41
+ cmd.options.should == { :mode => "FAILSAFE" }
42
+ end
43
+
44
+ should "allow commands to have aliases" do
45
+ SampleCmd.aliases "smp", "sampoo"
46
+ Command.create("smp").class.should == SampleCmd
47
+ Command.create("sampoo").class.should == SampleCmd
48
+ end
49
+
50
+ should "be valid if only a default subcommand exists" do
51
+ SampleCmd.new.should be_valid
52
+ end
53
+
54
+ should "allow named parameters" do
55
+ class NewCmd < Command
56
+ subcommand :move, :required_parameters => [:provider, :server_name]
57
+ end
58
+
59
+ lambda {
60
+ cmd = NewCmd.new(["move"])
61
+ }.should raise_error(Exceptions::InvalidCommand, /requires 2 parameters/)
62
+ end
63
+
64
+ should "inflex the requirement error" do
65
+ class NewCmd < Command
66
+ subcommand :move, :required_parameters => [:server_name]
67
+ end
68
+
69
+ lambda {
70
+ cmd = NewCmd.new(["move"])
71
+ }.should raise_error(Exceptions::InvalidCommand, /requires 1 parameter\./)
72
+ end
73
+
74
+ should "allow subcommand aliases" do
75
+ class Cmd < Command
76
+ alias_subcommand :list => :default
77
+ end
78
+
79
+ cmd = Cmd.new(["list"])
80
+ cmd.subcommand.should == :default
81
+ end
82
+
83
+ should "throw a specific exception when subcommand doesn't exist" do
84
+ class ListCommand < Command
85
+ end
86
+
87
+ lambda {
88
+ ListCommand.new(["find"]).run
89
+ }.should raise_error(Exceptions::InvalidCommand, /Command list doesn't have a subcommand "find". Try "stackfu list help" for more information./)
90
+ end
91
+
92
+ should "allow the error message for inexistent subcommand to be customized" do
93
+ class ExampleCommand < Command
94
+ error_messages :missing_subcommand => "There's no such thing as %s"
95
+ end
96
+
97
+ lambda {
98
+ ExampleCommand.new(["test"]).run
99
+ }.should raise_error(Exceptions::InvalidCommand, /There's no such thing as test/)
100
+ end
101
+
102
+ should "allow the error message for missing params to be customized" do
103
+ class ExampleCommand < Command
104
+ subcommand :test, :required_parameters => [:sex, :age]
105
+ error_messages :missing_params => "You have to provide sex and age, dude!"
106
+ end
107
+
108
+ lambda {
109
+ ExampleCommand.new(["test"]).run
110
+ }.should raise_error(Exceptions::InvalidCommand, /You have to provide sex and age, dude!/)
111
+ end
112
+ end
@@ -0,0 +1,92 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper.rb'
2
+
3
+ class TestConfigCommand < Test::Unit::TestCase
4
+ context "provider command" do
5
+ should "recognize Webbynode" do
6
+ with_users
7
+ with_users_update("4afe06b8e1054e1e00000001")
8
+
9
+ when_asked "Webbynode Login:", :answer => "fcoury@me.com"
10
+ when_asked "Webbynode Token:", :answer => "abc123"
11
+
12
+ command "config webbynode"
13
+ stdout.should =~ /Webbynode credentials saved./
14
+ end
15
+
16
+ should "recognize Webbynode" do
17
+ with_users
18
+ with_users_update("4afe06b8e1054e1e00000001")
19
+
20
+ when_asked "Webbynode Login:", :answer => "fcoury@me.com"
21
+ when_asked "Webbynode Token:", :answer => "abc123"
22
+
23
+ command "config webbynode"
24
+ stdout.should =~ /Webbynode credentials saved./
25
+ end
26
+
27
+ should "recognize Slicehost" do
28
+ with_users
29
+ with_users_update("4afe06b8e1054e1e00000001")
30
+
31
+ when_asked "", :answer => "6ed05e7521dfb6a19c98a84f2d0a28fdffc1bf00e050fe7114a66d3049e9715e"
32
+
33
+ command "config slicehost"
34
+ stdout.should =~ /Slicehost credentials saved./
35
+ end
36
+ end
37
+
38
+ context "default command" do
39
+ should "skip asking login, token and confirmation if --login and --token provided" do
40
+ ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
41
+ command "config --login=flipper --token=abc123"
42
+ stdout.should =~ /Configuration saved/
43
+ end
44
+
45
+ should "skip asking login if --login provided" do
46
+ when_asked "StackFu Token: ", :answer => "abc123"
47
+
48
+ agree_with "Is this information correct? "
49
+
50
+ ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
51
+ command "config --login=flipper"
52
+ stdout.should =~ /Configuration saved/
53
+ end
54
+
55
+ should "skip asking token if --token provided" do
56
+ when_asked "StackFu Login: ", :answer => "flipper"
57
+
58
+ agree_with "Is this information correct? "
59
+
60
+ ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
61
+ command "config --token=abc123"
62
+ stdout.should =~ /Configuration saved/
63
+ end
64
+
65
+ should "Ask the login, token and offer to add a new server" do
66
+ when_asked "StackFu Login: ", :answer => "flipper"
67
+ when_asked "StackFu Token: ", :answer => "abc123"
68
+
69
+ agree_with "Is this information correct? "
70
+
71
+ ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
72
+ command "config"
73
+ stdout.should =~ /Configuration saved/
74
+ end
75
+
76
+ should "Ask the login, token again if information not correct" do
77
+ when_asked "StackFu Login: ", :answer => "flipper"
78
+ when_asked "StackFu Token: ", :answer => "abc123"
79
+
80
+ disagree_of "Is this information correct? "
81
+
82
+ when_asked "StackFu Login: ", :answer => "flipper"
83
+ when_asked "StackFu Token: ", :answer => "abc123"
84
+
85
+ agree_with "Is this information correct? "
86
+
87
+ ConfigCommand.any_instance.expects(:save_config).with("flipper", "abc123")
88
+ command "config"
89
+ stdout.should =~ /Configuration saved/
90
+ end
91
+ end
92
+ end