pbox 1.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/COPYRIGHT +1 -0
- data/LICENSE +11 -0
- data/README.md +40 -0
- data/Rakefile +6 -0
- data/autocomplete/pbox_bash +1639 -0
- data/bin/pbox +37 -0
- data/conf/protonbox.conf +8 -0
- data/features/assets/deploy.tar.gz +0 -0
- data/features/core_feature.rb +178 -0
- data/features/deployments_feature.rb +127 -0
- data/features/domains_feature.rb +49 -0
- data/features/keys_feature.rb +37 -0
- data/features/members_feature.rb +166 -0
- data/lib/rhc/auth/basic.rb +64 -0
- data/lib/rhc/auth/token.rb +102 -0
- data/lib/rhc/auth/token_store.rb +53 -0
- data/lib/rhc/auth.rb +5 -0
- data/lib/rhc/autocomplete.rb +66 -0
- data/lib/rhc/autocomplete_templates/bash.erb +39 -0
- data/lib/rhc/cartridge_helpers.rb +118 -0
- data/lib/rhc/cli.rb +40 -0
- data/lib/rhc/command_runner.rb +186 -0
- data/lib/rhc/commands/account.rb +25 -0
- data/lib/rhc/commands/alias.rb +124 -0
- data/lib/rhc/commands/app.rb +701 -0
- data/lib/rhc/commands/apps.rb +20 -0
- data/lib/rhc/commands/authorization.rb +96 -0
- data/lib/rhc/commands/base.rb +174 -0
- data/lib/rhc/commands/cartridge.rb +326 -0
- data/lib/rhc/commands/deployment.rb +82 -0
- data/lib/rhc/commands/domain.rb +167 -0
- data/lib/rhc/commands/env.rb +142 -0
- data/lib/rhc/commands/git_clone.rb +29 -0
- data/lib/rhc/commands/logout.rb +51 -0
- data/lib/rhc/commands/member.rb +148 -0
- data/lib/rhc/commands/port_forward.rb +197 -0
- data/lib/rhc/commands/server.rb +40 -0
- data/lib/rhc/commands/setup.rb +60 -0
- data/lib/rhc/commands/snapshot.rb +137 -0
- data/lib/rhc/commands/ssh.rb +51 -0
- data/lib/rhc/commands/sshkey.rb +97 -0
- data/lib/rhc/commands/tail.rb +47 -0
- data/lib/rhc/commands/threaddump.rb +14 -0
- data/lib/rhc/commands.rb +396 -0
- data/lib/rhc/config.rb +320 -0
- data/lib/rhc/context_helper.rb +121 -0
- data/lib/rhc/core_ext.rb +202 -0
- data/lib/rhc/coverage_helper.rb +33 -0
- data/lib/rhc/deployment_helpers.rb +88 -0
- data/lib/rhc/exceptions.rb +232 -0
- data/lib/rhc/git_helpers.rb +91 -0
- data/lib/rhc/help_formatter.rb +55 -0
- data/lib/rhc/helpers.rb +477 -0
- data/lib/rhc/highline_extensions.rb +479 -0
- data/lib/rhc/json.rb +51 -0
- data/lib/rhc/output_helpers.rb +260 -0
- data/lib/rhc/rest/activation.rb +11 -0
- data/lib/rhc/rest/alias.rb +42 -0
- data/lib/rhc/rest/api.rb +87 -0
- data/lib/rhc/rest/application.rb +332 -0
- data/lib/rhc/rest/attributes.rb +36 -0
- data/lib/rhc/rest/authorization.rb +8 -0
- data/lib/rhc/rest/base.rb +79 -0
- data/lib/rhc/rest/cartridge.rb +154 -0
- data/lib/rhc/rest/client.rb +650 -0
- data/lib/rhc/rest/deployment.rb +18 -0
- data/lib/rhc/rest/domain.rb +98 -0
- data/lib/rhc/rest/environment_variable.rb +15 -0
- data/lib/rhc/rest/gear_group.rb +16 -0
- data/lib/rhc/rest/httpclient.rb +145 -0
- data/lib/rhc/rest/key.rb +44 -0
- data/lib/rhc/rest/membership.rb +105 -0
- data/lib/rhc/rest/mock.rb +1024 -0
- data/lib/rhc/rest/user.rb +32 -0
- data/lib/rhc/rest.rb +148 -0
- data/lib/rhc/ssh_helpers.rb +378 -0
- data/lib/rhc/tar_gz.rb +51 -0
- data/lib/rhc/usage_templates/command_help.erb +51 -0
- data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
- data/lib/rhc/usage_templates/help.erb +35 -0
- data/lib/rhc/usage_templates/missing_help.erb +1 -0
- data/lib/rhc/usage_templates/options_help.erb +12 -0
- data/lib/rhc/vendor/okjson.rb +600 -0
- data/lib/rhc/vendor/parseconfig.rb +178 -0
- data/lib/rhc/vendor/sshkey.rb +253 -0
- data/lib/rhc/vendor/zliby.rb +628 -0
- data/lib/rhc/version.rb +5 -0
- data/lib/rhc/wizard.rb +633 -0
- data/lib/rhc.rb +34 -0
- data/spec/coverage_helper.rb +89 -0
- data/spec/direct_execution_helper.rb +338 -0
- data/spec/keys/example.pem +23 -0
- data/spec/keys/example_private.pem +27 -0
- data/spec/keys/server.pem +19 -0
- data/spec/rest_spec_helper.rb +31 -0
- data/spec/rhc/assets/cert.crt +22 -0
- data/spec/rhc/assets/cert_key_rsa +27 -0
- data/spec/rhc/assets/empty.txt +0 -0
- data/spec/rhc/assets/env_vars.txt +7 -0
- data/spec/rhc/assets/env_vars_2.txt +1 -0
- data/spec/rhc/assets/foo.txt +1 -0
- data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
- data/spec/rhc/assets/targz_sample.tar.gz +0 -0
- data/spec/rhc/auth_spec.rb +442 -0
- data/spec/rhc/cli_spec.rb +188 -0
- data/spec/rhc/command_spec.rb +435 -0
- data/spec/rhc/commands/account_spec.rb +42 -0
- data/spec/rhc/commands/alias_spec.rb +333 -0
- data/spec/rhc/commands/app_spec.rb +754 -0
- data/spec/rhc/commands/apps_spec.rb +39 -0
- data/spec/rhc/commands/authorization_spec.rb +145 -0
- data/spec/rhc/commands/cartridge_spec.rb +641 -0
- data/spec/rhc/commands/deployment_spec.rb +286 -0
- data/spec/rhc/commands/domain_spec.rb +383 -0
- data/spec/rhc/commands/env_spec.rb +493 -0
- data/spec/rhc/commands/git_clone_spec.rb +80 -0
- data/spec/rhc/commands/logout_spec.rb +86 -0
- data/spec/rhc/commands/member_spec.rb +228 -0
- data/spec/rhc/commands/port_forward_spec.rb +217 -0
- data/spec/rhc/commands/server_spec.rb +69 -0
- data/spec/rhc/commands/setup_spec.rb +118 -0
- data/spec/rhc/commands/snapshot_spec.rb +179 -0
- data/spec/rhc/commands/ssh_spec.rb +163 -0
- data/spec/rhc/commands/sshkey_spec.rb +188 -0
- data/spec/rhc/commands/tail_spec.rb +81 -0
- data/spec/rhc/commands/threaddump_spec.rb +84 -0
- data/spec/rhc/config_spec.rb +407 -0
- data/spec/rhc/helpers_spec.rb +524 -0
- data/spec/rhc/highline_extensions_spec.rb +314 -0
- data/spec/rhc/json_spec.rb +30 -0
- data/spec/rhc/rest_application_spec.rb +248 -0
- data/spec/rhc/rest_client_spec.rb +752 -0
- data/spec/rhc/rest_spec.rb +740 -0
- data/spec/rhc/targz_spec.rb +55 -0
- data/spec/rhc/wizard_spec.rb +756 -0
- data/spec/spec_helper.rb +575 -0
- data/spec/wizard_spec_helper.rb +330 -0
- metadata +435 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rest_spec_helper'
|
|
3
|
+
require 'rhc/commands/member'
|
|
4
|
+
|
|
5
|
+
describe RHC::Commands::Member do
|
|
6
|
+
|
|
7
|
+
before{ user_config }
|
|
8
|
+
|
|
9
|
+
describe 'help' do
|
|
10
|
+
let(:arguments) { ['member', '--help'] }
|
|
11
|
+
|
|
12
|
+
it "should display help" do
|
|
13
|
+
expect { run }.to exit_with_code(0)
|
|
14
|
+
end
|
|
15
|
+
it('should output usage') { run_output.should match "Usage: pbox member" }
|
|
16
|
+
it('should output info about roles') { run_output.should match "Teams of developers can collaborate" }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:username){ 'test_user' }
|
|
20
|
+
let(:password){ 'test_password' }
|
|
21
|
+
let(:server){ 'test.domain.com' }
|
|
22
|
+
|
|
23
|
+
def with_mock_rest_client
|
|
24
|
+
@rest_client ||= MockRestClient.new
|
|
25
|
+
end
|
|
26
|
+
def with_mock_domain
|
|
27
|
+
@domain ||= with_mock_rest_client.add_domain("mock-domain-0")
|
|
28
|
+
end
|
|
29
|
+
def with_mock_app
|
|
30
|
+
@app ||= begin
|
|
31
|
+
app = with_mock_domain.add_application("mock-app-0", "ruby-1.8.7")
|
|
32
|
+
app.stub(:ssh_url).and_return("ssh://user@test.domain.com")
|
|
33
|
+
app.stub(:supports_members?).and_return(supports_members)
|
|
34
|
+
app
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
let(:owner){ RHC::Rest::Membership::Member.new(:id => '1', :role => 'admin', :owner => true, :login => 'alice') }
|
|
39
|
+
let(:other_admin){ RHC::Rest::Membership::Member.new(:id => '2', :role => 'admin', :login => 'Bob') }
|
|
40
|
+
let(:other_editor){ RHC::Rest::Membership::Member.new(:id => '3', :role => 'editor', :name => 'Carol', :login => 'carol') }
|
|
41
|
+
let(:other_viewer){ RHC::Rest::Membership::Member.new(:id => '4', :role => 'viewer', :name => 'Doug', :login => 'doug@doug.com') }
|
|
42
|
+
|
|
43
|
+
describe 'list-member' do
|
|
44
|
+
context 'on a domain' do
|
|
45
|
+
let(:arguments) { ['members', '-n', 'mock-domain-0'] }
|
|
46
|
+
let(:supports_members){ true }
|
|
47
|
+
before{ with_mock_domain.add_member(owner) }
|
|
48
|
+
it { expect { run }.to exit_with_code(0) }
|
|
49
|
+
it { run_output.should =~ /alice\s+admin \(owner\)/ }
|
|
50
|
+
it("should not show the name column") { run_output.should =~ /^Login\s+Role$/ }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context 'on an application' do
|
|
54
|
+
let(:arguments) { ['members', 'mock-domain-0/mock-app-0'] }
|
|
55
|
+
let(:supports_members){ false }
|
|
56
|
+
before{ with_mock_app }
|
|
57
|
+
|
|
58
|
+
it { expect { run }.to exit_with_code(1) }
|
|
59
|
+
it { run_output.should =~ /The server does not support adding or removing members/ }
|
|
60
|
+
|
|
61
|
+
context "with only owner" do
|
|
62
|
+
let(:supports_members){ true }
|
|
63
|
+
before{ with_mock_app.add_member(owner) }
|
|
64
|
+
it { expect { run }.to exit_with_code(0) }
|
|
65
|
+
it { run_output.should =~ /alice\s+admin \(owner\)/ }
|
|
66
|
+
it("should not show the name column") { run_output.should =~ /^Login\s+Role$/ }
|
|
67
|
+
|
|
68
|
+
context "with ids" do
|
|
69
|
+
let(:arguments) { ['members', 'mock-domain-0/mock-app-0', '--ids'] }
|
|
70
|
+
it { expect { run }.to exit_with_code(0) }
|
|
71
|
+
it { run_output.should =~ /alice\s+admin \(owner\) 1/ }
|
|
72
|
+
it("should not show the name column") { run_output.should =~ /^Login\s+Role\s+ID$/ }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "with several members" do
|
|
77
|
+
let(:supports_members){ true }
|
|
78
|
+
before{ with_mock_app.add_member(owner).add_member(other_editor).add_member(other_admin).add_member(other_viewer) }
|
|
79
|
+
it { expect { run }.to exit_with_code(0) }
|
|
80
|
+
it { run_output.should =~ /alice\s+admin \(owner\)/ }
|
|
81
|
+
it { run_output.should =~ /Bob\s+admin/ }
|
|
82
|
+
it { run_output.should =~ /carol\s+editor/ }
|
|
83
|
+
it { run_output.should =~ /doug\.com\s+viewer/ }
|
|
84
|
+
it("should order the members by role") { run_output.should =~ /admin.*owner.*admin.*edit.*view/m }
|
|
85
|
+
it("should include the login value") { run_output.should =~ /alice.*Bob.*carol.*doug@doug\.com/m }
|
|
86
|
+
it("should show the name column") { run_output.should =~ /^Name\s+Login\s+Role$/ }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe 'add-member' do
|
|
92
|
+
before do
|
|
93
|
+
stub_api
|
|
94
|
+
challenge{ stub_one_domain('test', nil, mock_user_auth) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context "when the resource doesn't support membership changes" do
|
|
98
|
+
let(:arguments) { ['add-member', 'testuser', '-n', 'test'] }
|
|
99
|
+
it { expect { run }.to exit_with_code(1) }
|
|
100
|
+
it { run_output.should =~ /Adding 1 editor to domain .*The server does not support adding or removing members/ }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context "with supported membership" do
|
|
104
|
+
let(:supports_members?){ true }
|
|
105
|
+
|
|
106
|
+
context 'with a valid user' do
|
|
107
|
+
let(:arguments) { ['add-member', 'testuser', '-n', 'test'] }
|
|
108
|
+
before do
|
|
109
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
|
110
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'edit'}]}).
|
|
111
|
+
to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Added 1 member'},]}.to_json, :status => 200})
|
|
112
|
+
end
|
|
113
|
+
it { expect { run }.to exit_with_code(0) }
|
|
114
|
+
it { run_output.should =~ /Adding 1 editor to domain .*done/ }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context 'with an invalid role' do
|
|
118
|
+
let(:arguments) { ['add-member', 'testuser', '-n', 'test', '--role', 'missing'] }
|
|
119
|
+
it { expect { run }.to exit_with_code(1) }
|
|
120
|
+
it { run_output.should =~ /The provided role 'missing' is not valid\. Supported values: .*admin/ }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
context 'with a missing user' do
|
|
124
|
+
let(:arguments) { ['add-member', 'testuser', '-n', 'test'] }
|
|
125
|
+
before do
|
|
126
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
|
127
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'edit'}]}).
|
|
128
|
+
to_return({:body => {:messages => [{:exit_code => 132, :field => 'login', :index => 0, :severity => 'error', :text => 'There is no user with a login testuser'},]}.to_json, :status => 422})
|
|
129
|
+
end
|
|
130
|
+
it { expect { run }.to exit_with_code(1) }
|
|
131
|
+
it { run_output.should =~ /Adding 1 editor to domain.*There is no user with a login testuser/ }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context 'with a missing user id and role' do
|
|
135
|
+
let(:arguments) { ['add-member', '123', '-n', 'test', '--ids', '--role', 'admin'] }
|
|
136
|
+
before do
|
|
137
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
|
138
|
+
with(:body => {:members => [{'id' => '123', 'role' => 'admin'}]}).
|
|
139
|
+
to_return({:body => {:messages => [{:exit_code => 132, :field => 'id', :index => 0, :severity => 'error', :text => 'There is no user with the id 123'},]}.to_json, :status => 422})
|
|
140
|
+
end
|
|
141
|
+
it { expect { run }.to exit_with_code(1) }
|
|
142
|
+
it { run_output.should =~ /Adding 1 administrator to domain.*There is no user with the id 123/ }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe 'remove-member' do
|
|
148
|
+
context "when the resource doesn't support membership changes" do
|
|
149
|
+
before{ stub_api }
|
|
150
|
+
|
|
151
|
+
context "when adjusting a domain" do
|
|
152
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] }
|
|
153
|
+
before{ challenge{ stub_one_domain('test', nil, mock_user_auth) } }
|
|
154
|
+
it { expect { run }.to exit_with_code(1) }
|
|
155
|
+
it { run_output.should =~ /Removing 1 member from domain .*The server does not support adding or removing members/ }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context "with supported membership" do
|
|
160
|
+
let(:supports_members?){ true }
|
|
161
|
+
before do
|
|
162
|
+
stub_api
|
|
163
|
+
challenge{ stub_one_domain('test', nil, mock_user_auth) }
|
|
164
|
+
end
|
|
165
|
+
=begin Scenario removed
|
|
166
|
+
context "when adjusting an app" do
|
|
167
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test', '-a', 'app'] }
|
|
168
|
+
before{ challenge{ stub_one_application('test', 'app') } }
|
|
169
|
+
it { expect { run }.to exit_with_code(1) }
|
|
170
|
+
it { run_output.should =~ /You can only add or remove members on a domain/ }
|
|
171
|
+
end
|
|
172
|
+
=end
|
|
173
|
+
context 'with a valid member' do
|
|
174
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] }
|
|
175
|
+
before do
|
|
176
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
|
177
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'none'}]}).
|
|
178
|
+
to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Removed 1 member'},]}.to_json, :status => 200})
|
|
179
|
+
end
|
|
180
|
+
it { expect { run }.to exit_with_code(0) }
|
|
181
|
+
it { run_output.should =~ /Removing 1 member from domain .*done/ }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
context 'with --all' do
|
|
185
|
+
let(:arguments) { ['remove-member', '--all', '-n', 'test'] }
|
|
186
|
+
before do
|
|
187
|
+
stub_api_request(:delete, "broker/rest/domains/test/members").
|
|
188
|
+
to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Removed everyone except owner.'},]}.to_json, :status => 200})
|
|
189
|
+
end
|
|
190
|
+
it { expect { run }.to exit_with_code(0) }
|
|
191
|
+
it { run_output.should =~ /Removing all members from domain .*done/ }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context 'with a missing user' do
|
|
195
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] }
|
|
196
|
+
before do
|
|
197
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
|
198
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'none'}]}).
|
|
199
|
+
to_return({:body => {:messages => [{:exit_code => 132, :field => 'login', :index => 0, :severity => 'error', :text => 'There is no user with a login testuser'},]}.to_json, :status => 422})
|
|
200
|
+
end
|
|
201
|
+
it { expect { run }.to exit_with_code(1) }
|
|
202
|
+
it { run_output.should =~ /Removing 1 member from domain.*There is no user with a login testuser/ }
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context 'with a missing user id and role' do
|
|
206
|
+
let(:arguments) { ['remove-member', '123', '-n', 'test', '--ids'] }
|
|
207
|
+
before do
|
|
208
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
|
209
|
+
with(:body => {:members => [{'id' => '123', 'role' => 'none'}]}).
|
|
210
|
+
to_return({:body => {:messages => [{:exit_code => 132, :field => 'id', :index => 0, :severity => 'error', :text => 'There is no user with the id 123'},]}.to_json, :status => 422})
|
|
211
|
+
end
|
|
212
|
+
it { expect { run }.to exit_with_code(1) }
|
|
213
|
+
it { run_output.should =~ /Removing 1 member from domain.*There is no user with the id 123/ }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
context 'when the user isn''t a member' do
|
|
217
|
+
let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] }
|
|
218
|
+
before do
|
|
219
|
+
stub_api_request(:patch, "broker/rest/domains/test/members").
|
|
220
|
+
with(:body => {:members => [{'login' => 'testuser', 'role' => 'none'}]}).
|
|
221
|
+
to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'warning', :text => 'testuser is not a member of this domain.'},]}.to_json, :status => 200})
|
|
222
|
+
end
|
|
223
|
+
it { expect { run }.to exit_with_code(0) }
|
|
224
|
+
it { run_output.should =~ /Removing 1 member from domain.*testuser is not a member of this domain.*done/m }
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rest_spec_helper'
|
|
3
|
+
require 'rhc/commands/port_forward'
|
|
4
|
+
|
|
5
|
+
describe RHC::Commands::PortForward do
|
|
6
|
+
|
|
7
|
+
let!(:rest_client){ MockRestClient.new }
|
|
8
|
+
before{ user_config }
|
|
9
|
+
|
|
10
|
+
describe 'run' do
|
|
11
|
+
let(:arguments) { ['port-forward', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp'] }
|
|
12
|
+
|
|
13
|
+
before :each do
|
|
14
|
+
@domain = rest_client.add_domain("mockdomain")
|
|
15
|
+
@app = @domain.add_application 'mockapp', 'mock-1.0'
|
|
16
|
+
@uri = URI.parse @app.ssh_url
|
|
17
|
+
@ssh = double(Net::SSH)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context 'when port forwarding for a down appl' do
|
|
21
|
+
before(:each) do
|
|
22
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh)
|
|
23
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports").and_yield(nil, :stderr, '127.0.0.1:3306')
|
|
24
|
+
@gg = MockRestGearGroup.new(rest_client)
|
|
25
|
+
@app.should_receive(:gear_groups).and_return([@gg])
|
|
26
|
+
@gg.should_receive(:gears).and_return([{'state' => 'stopped', 'id' => 'fakegearid'}])
|
|
27
|
+
end
|
|
28
|
+
it "should error out and suggest restarting the application" do
|
|
29
|
+
expect { run }.to exit_with_code(1)
|
|
30
|
+
end
|
|
31
|
+
it { run_output.should match(/none.*The application is stopped\..*restart/m) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'when port forwarding an app without ports to forward' do
|
|
35
|
+
before(:each) do
|
|
36
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh)
|
|
37
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports").and_yield(nil, :stderr, '127.0.0.1:3306')
|
|
38
|
+
end
|
|
39
|
+
it "should error out as no ports to forward" do
|
|
40
|
+
expect { run }.to exit_with_code(102)
|
|
41
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
|
42
|
+
rest_client.domains[0].applications.size.should == 1
|
|
43
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
|
44
|
+
end
|
|
45
|
+
it("should report no ports") { run_output.should match("no available ports to forward.") }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context 'when port forwarding an app with permission denied ports' do
|
|
49
|
+
before(:each) do
|
|
50
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh)
|
|
51
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports").and_yield(nil, :stderr, 'permission denied')
|
|
52
|
+
end
|
|
53
|
+
it "should error out as permission denied" do
|
|
54
|
+
expect { run }.to exit_with_code(129)
|
|
55
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
|
56
|
+
rest_client.domains[0].applications.size.should == 1
|
|
57
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
|
58
|
+
end
|
|
59
|
+
it { run_output.should match("Permission denied") }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'when port forwarding an app with ports to forward' do
|
|
63
|
+
before(:each) do
|
|
64
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
|
65
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306')
|
|
66
|
+
forward = double(Net::SSH::Service::Forward)
|
|
67
|
+
@ssh.should_receive(:forward).and_return(forward)
|
|
68
|
+
forward.should_receive(:local).with(3306, '127.0.0.1', 3306)
|
|
69
|
+
@ssh.should_receive(:loop)
|
|
70
|
+
end
|
|
71
|
+
it "should run successfully" do
|
|
72
|
+
expect { run }.to exit_with_code(0)
|
|
73
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
|
74
|
+
rest_client.domains[0].applications.size.should == 1
|
|
75
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
|
76
|
+
end
|
|
77
|
+
it { run_output.should match(/Forwarding ports.*Press CTRL-C/m) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'when host is unreachable' do
|
|
81
|
+
before(:each) do
|
|
82
|
+
Net::SSH.should_receive(:start).and_raise(Errno::EHOSTUNREACH)
|
|
83
|
+
end
|
|
84
|
+
it "should error out" do
|
|
85
|
+
expect { run }.to exit_with_code(1)
|
|
86
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
|
87
|
+
rest_client.domains[0].applications.size.should == 1
|
|
88
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
|
89
|
+
end
|
|
90
|
+
it { run_output.should include("Error trying to forward ports.") }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
context 'when REST client connection times out' do
|
|
94
|
+
before(:each) do
|
|
95
|
+
rest_client.should_receive(:find_domain).and_raise(RHC::Rest::ConnectionException)
|
|
96
|
+
end
|
|
97
|
+
it("should error out") { expect { run }.to exit_with_code(1) }
|
|
98
|
+
it{ run_output.should match("Connection.*failed:") }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
context 'when port forwarding an app with ports to forward' do
|
|
102
|
+
before(:each) do
|
|
103
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
|
104
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306')
|
|
105
|
+
forward = double(Net::SSH::Service::Forward)
|
|
106
|
+
@ssh.should_receive(:forward).and_return(forward)
|
|
107
|
+
forward.should_receive(:local).with(3306, '127.0.0.1', 3306)
|
|
108
|
+
@ssh.should_receive(:loop).and_raise(Interrupt.new)
|
|
109
|
+
end
|
|
110
|
+
it "should exit when user interrupts" do
|
|
111
|
+
expect { run }.to exit_with_code(0)
|
|
112
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
|
113
|
+
rest_client.domains[0].applications.size.should == 1
|
|
114
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
|
115
|
+
end
|
|
116
|
+
it { run_output.should include("Ending port forward") }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
context 'when local port is already bound' do
|
|
120
|
+
before(:each) do
|
|
121
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
|
122
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306')
|
|
123
|
+
forward = double(Net::SSH::Service::Forward)
|
|
124
|
+
@ssh.should_receive(:forward).at_least(2).and_return(forward)
|
|
125
|
+
forward.should_receive(:local).with(3306, '127.0.0.1', 3306).and_raise(Errno::EACCES)
|
|
126
|
+
forward.should_receive(:local).with(3307, '127.0.0.1', 3306)
|
|
127
|
+
@ssh.should_receive(:loop).and_raise(Interrupt.new)
|
|
128
|
+
end
|
|
129
|
+
it 'should bind to a higher port' do
|
|
130
|
+
run_output.should include("3307")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context 'when host refuses connection' do
|
|
135
|
+
before(:each) do
|
|
136
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
|
137
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306')
|
|
138
|
+
forward = double(Net::SSH::Service::Forward)
|
|
139
|
+
@ssh.should_receive(:forward).and_raise(Errno::ECONNREFUSED)
|
|
140
|
+
end
|
|
141
|
+
it "should error out" do
|
|
142
|
+
expect { run }.to exit_with_code(0)
|
|
143
|
+
end
|
|
144
|
+
it { run_output.should include("ssh -N") }
|
|
145
|
+
it { run_output.should include("Error forwarding") }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
context 'when port forwarding a scaled app with ports to forward' do
|
|
149
|
+
let(:haproxy_host_1) { '127.0.0.1' }
|
|
150
|
+
let(:haproxy_host_2) { '127.0.0.2' }
|
|
151
|
+
let(:mongo_host) { '51125bb94a-test907742.dev.protonbox.me' }
|
|
152
|
+
let(:ipv6_host) { '::1' }
|
|
153
|
+
before(:each) do
|
|
154
|
+
Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice
|
|
155
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports").
|
|
156
|
+
and_yield(nil, :stderr, "httpd -> #{haproxy_host_1}:8080\nhttpd -> #{haproxy_host_2}:8080\nmongodb -> #{mongo_host}:35541\nmysqld -> #{ipv6_host}:3306")
|
|
157
|
+
forward = double(Net::SSH::Service::Forward)
|
|
158
|
+
@ssh.should_receive(:forward).at_least(3).times.and_return(forward)
|
|
159
|
+
forward.should_receive(:local).with(8080, haproxy_host_1, 8080)
|
|
160
|
+
forward.should_receive(:local).with(8080, haproxy_host_2, 8080).and_raise(Errno::EADDRINUSE)
|
|
161
|
+
forward.should_receive(:local).with(8081, haproxy_host_2, 8080)
|
|
162
|
+
forward.should_receive(:local).with(35541, mongo_host, 35541)
|
|
163
|
+
forward.should_receive(:local).with(3306, ipv6_host, 3306)
|
|
164
|
+
@ssh.should_receive(:loop).and_raise(Interrupt.new)
|
|
165
|
+
end
|
|
166
|
+
it "should exit when user interrupts" do
|
|
167
|
+
expect { run }.to exit_with_code(0)
|
|
168
|
+
rest_client.domains[0].name.should == 'mockdomain'
|
|
169
|
+
rest_client.domains[0].applications.size.should == 1
|
|
170
|
+
rest_client.domains[0].applications[0].name.should == 'mockapp'
|
|
171
|
+
end
|
|
172
|
+
it { run_output.should include("Ending port forward") }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
context 'when port forwarding a single gear on a scaled app' do
|
|
176
|
+
let(:gear_host) { 'fakesshurl.com' }
|
|
177
|
+
let(:gear_user) { 'fakegearid0' }
|
|
178
|
+
let(:arguments) { ['port-forward', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--gear', @gear_id] }
|
|
179
|
+
|
|
180
|
+
it 'should run successfully' do
|
|
181
|
+
@gear_id = 'fakegearid0'
|
|
182
|
+
Net::SSH.should_receive(:start).with(gear_host, gear_user).and_yield(@ssh).twice
|
|
183
|
+
|
|
184
|
+
@ssh.should_receive(:exec!).with("pbox-list-ports --exclude-remote").
|
|
185
|
+
and_yield(nil, :stderr, "mongodb -> #{gear_host}:35541")
|
|
186
|
+
forward = double(Net::SSH::Service::Forward)
|
|
187
|
+
@ssh.should_receive(:forward).and_return(forward)
|
|
188
|
+
forward.should_receive(:local).with(35541, gear_host, 35541)
|
|
189
|
+
@ssh.should_receive(:loop).and_raise(Interrupt.new)
|
|
190
|
+
|
|
191
|
+
expect { run }.to exit_with_code(0)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'should fail if the specified gear is missing' do
|
|
195
|
+
@gear_id = 'notarealgearxxxxx'
|
|
196
|
+
|
|
197
|
+
expect { run }.to exit_with_code(1)
|
|
198
|
+
run_output.should include('Gear notarealgearxxxxx not found')
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it 'should fail if the specified gear has no ssh info' do
|
|
202
|
+
@gear_id = 'fakegearid0'
|
|
203
|
+
|
|
204
|
+
# Given - gears in gear group should not have ssh info
|
|
205
|
+
gg = MockRestGearGroup.new(rest_client)
|
|
206
|
+
@app.stub(:gear_groups).and_return([gg])
|
|
207
|
+
gg.stub(:gears).and_return([{'state' => 'started', 'id' => 'fakegearid0'}])
|
|
208
|
+
|
|
209
|
+
expect { run }.to exit_with_code(1)
|
|
210
|
+
run_output.should match('The server does not support operations on individual gears.')
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rest_spec_helper'
|
|
3
|
+
require 'rhc/commands/server'
|
|
4
|
+
require 'rhc/config'
|
|
5
|
+
|
|
6
|
+
describe RHC::Commands::Server do
|
|
7
|
+
before(:each){ user_config }
|
|
8
|
+
let(:rest_client) { MockRestClient.new }
|
|
9
|
+
|
|
10
|
+
describe 'run against a different server' do
|
|
11
|
+
let(:arguments) { ['server', '--server', 'foo.com', '-l', 'person', '-p', ''] }
|
|
12
|
+
|
|
13
|
+
context 'when server refuses connection' do
|
|
14
|
+
before { stub_request(:get, 'https://foo.com/broker/rest/api').with(&user_agent_header).to_raise(SocketError) }
|
|
15
|
+
it('should output an error') { run_output.should =~ /Connected to foo.com.*Unable to connect to the server/m }
|
|
16
|
+
it { expect { run }.to exit_with_code(1) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'when API is missing' do
|
|
20
|
+
before { stub_request(:get, 'https://foo.com/broker/rest/api').with(&user_agent_header).to_return(:status => 404) }
|
|
21
|
+
it('should output an error') { run_output.should =~ /Connected to foo.com.*server is not responding correctly/m }
|
|
22
|
+
it { expect { run }.to exit_with_code(1) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context 'when API is at version 1.2' do
|
|
26
|
+
before do
|
|
27
|
+
rest_client.stub(:api_version_negotiated).and_return('1.2')
|
|
28
|
+
end
|
|
29
|
+
it('should output an error') { run_output.should =~ /Connected to foo.com.*Using API version 1.2/m }
|
|
30
|
+
it { expect { run }.to exit_with_code(0) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe 'run against an invalid server url' do
|
|
35
|
+
let(:arguments) { ['server', '--server', 'invalid_uri', '-l', 'person', '-p', ''] }
|
|
36
|
+
it('should output an invalid URI error') { run_output.should match('Invalid URI specified: invalid_uri') }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe 'run' do
|
|
40
|
+
let(:arguments) { ['server'] }
|
|
41
|
+
before{ rest_client.stub(:auth).and_return(nil) }
|
|
42
|
+
|
|
43
|
+
context 'when no issues' do
|
|
44
|
+
before { stub_request(:get, 'https://api.protonbox.com/app/status/status.json').with(&user_agent_header).to_return(:body => {'issues' => []}.to_json) }
|
|
45
|
+
it('should output success') { run_output.should =~ /All systems running fine/ }
|
|
46
|
+
it { expect { run }.to exit_with_code(0) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'when 1 issue' do
|
|
50
|
+
before do
|
|
51
|
+
stub_request(:get, 'https://api.protonbox.com/app/status/status.json').with(&user_agent_header).to_return(:body =>
|
|
52
|
+
{'open' => [
|
|
53
|
+
{'issue' => {
|
|
54
|
+
'created_at' => '2011-05-22T17:31:32-04:00',
|
|
55
|
+
'id' => 11,
|
|
56
|
+
'title' => 'Root cause',
|
|
57
|
+
'updates' => [{
|
|
58
|
+
'created_at' => '2012-05-22T13:48:20-04:00',
|
|
59
|
+
'description' => 'Working on update'
|
|
60
|
+
}]
|
|
61
|
+
}}]}.to_json)
|
|
62
|
+
end
|
|
63
|
+
it { expect { run }.to exit_with_code(1) }
|
|
64
|
+
it('should output message') { run_output.should =~ /1 open issue/ }
|
|
65
|
+
it('should output title') { run_output.should =~ /Root cause/ }
|
|
66
|
+
it('should contain update') { run_output.should =~ /Working on update/ }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rest_spec_helper'
|
|
3
|
+
require 'rhc/commands/setup'
|
|
4
|
+
|
|
5
|
+
# just test the command runner as we already have extensive wizard tests
|
|
6
|
+
describe RHC::Commands::Setup do
|
|
7
|
+
subject{ RHC::Commands::Setup }
|
|
8
|
+
let(:instance){ subject.new }
|
|
9
|
+
let!(:config){ base_config }
|
|
10
|
+
before{ described_class.send(:public, *described_class.protected_instance_methods) }
|
|
11
|
+
before{ FakeFS::FileSystem.clear }
|
|
12
|
+
before{ RHC::Config.stub(:home_dir).and_return('/home/mock_user') }
|
|
13
|
+
|
|
14
|
+
describe '#run' do
|
|
15
|
+
it{ expects_running('setup').should call(:run).on(instance).with(no_args) }
|
|
16
|
+
|
|
17
|
+
let(:arguments) { ['setup', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password'] }
|
|
18
|
+
|
|
19
|
+
before(:each) do
|
|
20
|
+
@wizard = double('wizard')
|
|
21
|
+
@wizard.stub(:run).and_return(true)
|
|
22
|
+
RHC::RerunWizard.stub(:new){ @wizard }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context 'when no issues' do
|
|
26
|
+
it "should exit 0" do
|
|
27
|
+
expect { run }.to exit_with_code(0)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context 'when there is an issue' do
|
|
32
|
+
it "should exit 1" do
|
|
33
|
+
@wizard.stub(:run).and_return(false)
|
|
34
|
+
expect { run }.to exit_with_code(1)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it{ expects_running('setup').should call(:run).on(instance).with(no_args) }
|
|
40
|
+
it{ command_for('setup', '--clean').options.clean.should be_true }
|
|
41
|
+
|
|
42
|
+
it{ command_for('setup').options.server.should == 'api.protonbox.com' }
|
|
43
|
+
it{ command_for('setup', '--server', 'foo.com').options.server.should == 'foo.com' }
|
|
44
|
+
it{ command_for('setup', '--no-create-token').options.create_token.should == false }
|
|
45
|
+
it{ command_for('setup', '--create-token').options.create_token.should == true }
|
|
46
|
+
|
|
47
|
+
context "when config has use_authorization_tokens=false" do
|
|
48
|
+
let!(:config){ base_config{ |c, d| d.add('use_authorization_tokens', 'false') } }
|
|
49
|
+
it{ command_for('setup').options.use_authorization_tokens.should == false }
|
|
50
|
+
end
|
|
51
|
+
context "when config has use_authorization_tokens=true" do
|
|
52
|
+
let!(:config){ base_config{ |c, d| d.add('use_authorization_tokens', 'true') } }
|
|
53
|
+
it{ command_for('setup').options.use_authorization_tokens.should == true }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
=begin context 'when protonbox_server is set' do
|
|
57
|
+
before{ ENV.stub(:[]).with('PROTONBOX_SERVER').and_return('bar.com') }
|
|
58
|
+
it{ command_for('setup').config['protonbox_server'].should == 'bar.com' }
|
|
59
|
+
it{ command_for('setup').options.server.should == 'bar.com' }
|
|
60
|
+
it{ command_for('setup', '--server', 'foo.com').options.server.should == 'foo.com' }
|
|
61
|
+
=end end
|
|
62
|
+
|
|
63
|
+
context 'when --clean is used' do
|
|
64
|
+
let!(:config){ base_config{ |config, defaults| defaults.add 'protonbox_server', 'test.com' } }
|
|
65
|
+
|
|
66
|
+
it("should ignore a config value"){ command_for('setup', '--clean').options.server.should == 'api.protonbox.com' }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'when -d is passed' do
|
|
70
|
+
let(:arguments) { ['setup', '-d', '-l', 'test@test.foo'] }
|
|
71
|
+
# 'y' for the password prompt
|
|
72
|
+
let(:input) { ['', 'y', '', ''] }
|
|
73
|
+
let!(:rest_client){ MockRestClient.new }
|
|
74
|
+
|
|
75
|
+
it("succeeds"){ FakeFS{ expect { run input }.to exit_with_code 0 } }
|
|
76
|
+
it("the output includes debug output") do
|
|
77
|
+
FakeFS{ run_output( input ).should match 'DEBUG' }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context 'when -l is used to specify the user name' do
|
|
82
|
+
let(:arguments) { ['setup', '-l', 'test@test.foo'] }
|
|
83
|
+
# 'y' for the password prompt
|
|
84
|
+
let(:input) { ['', 'y', '', ''] }
|
|
85
|
+
let!(:rest_client){ MockRestClient.new }
|
|
86
|
+
|
|
87
|
+
it("succeeds"){ FakeFS{ expect { run input }.to exit_with_code 0 } }
|
|
88
|
+
it("sets the user name to the value given by the command line") do
|
|
89
|
+
FakeFS{ run_output( input ).should match 'test@test.foo' }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe 'help' do
|
|
94
|
+
let(:arguments) { ['setup', '--help'] }
|
|
95
|
+
|
|
96
|
+
context 'help is run' do
|
|
97
|
+
it "should display help" do
|
|
98
|
+
@wizard.stub(:run).and_return(true)
|
|
99
|
+
expect { run }.to exit_with_code(0)
|
|
100
|
+
end
|
|
101
|
+
it('should output usage') { run_output.should match("Connects to an ProtonBox server to get you started. Will") }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe '--autocomplete' do
|
|
106
|
+
let(:arguments) { ['setup', '--autocomplete'] }
|
|
107
|
+
before do
|
|
108
|
+
path = File.join(Gem.loaded_specs['pbox'].full_gem_path, "autocomplete")
|
|
109
|
+
FakeFS::FileUtils.mkdir_p(path)
|
|
110
|
+
FakeFS::FileUtils.touch(File.join(path, "pbox_bash"))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'is passed' do
|
|
114
|
+
it('should output information') { FakeFS{ run_output.should match("To enable tab-completion") } }
|
|
115
|
+
it('should output the gem path') { FakeFS{ run_output.should match File.join(RHC::Config.home_conf_dir, 'bash_autocomplete') } }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|