artofmission-heroku 1.6.3

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.
Files changed (47) hide show
  1. data/README.md +66 -0
  2. data/Rakefile +107 -0
  3. data/bin/heroku +15 -0
  4. data/lib/heroku.rb +5 -0
  5. data/lib/heroku/client.rb +487 -0
  6. data/lib/heroku/command.rb +96 -0
  7. data/lib/heroku/commands/account.rb +13 -0
  8. data/lib/heroku/commands/addons.rb +109 -0
  9. data/lib/heroku/commands/app.rb +239 -0
  10. data/lib/heroku/commands/auth.rb +137 -0
  11. data/lib/heroku/commands/base.rb +133 -0
  12. data/lib/heroku/commands/bundles.rb +51 -0
  13. data/lib/heroku/commands/config.rb +55 -0
  14. data/lib/heroku/commands/db.rb +129 -0
  15. data/lib/heroku/commands/domains.rb +31 -0
  16. data/lib/heroku/commands/help.rb +148 -0
  17. data/lib/heroku/commands/keys.rb +49 -0
  18. data/lib/heroku/commands/logs.rb +11 -0
  19. data/lib/heroku/commands/maintenance.rb +13 -0
  20. data/lib/heroku/commands/plugins.rb +25 -0
  21. data/lib/heroku/commands/ps.rb +37 -0
  22. data/lib/heroku/commands/service.rb +23 -0
  23. data/lib/heroku/commands/sharing.rb +29 -0
  24. data/lib/heroku/commands/ssl.rb +33 -0
  25. data/lib/heroku/commands/version.rb +7 -0
  26. data/lib/heroku/helpers.rb +23 -0
  27. data/lib/heroku/plugin.rb +65 -0
  28. data/spec/base.rb +23 -0
  29. data/spec/client_spec.rb +366 -0
  30. data/spec/command_spec.rb +15 -0
  31. data/spec/commands/addons_spec.rb +47 -0
  32. data/spec/commands/app_spec.rb +175 -0
  33. data/spec/commands/auth_spec.rb +104 -0
  34. data/spec/commands/base_spec.rb +114 -0
  35. data/spec/commands/bundles_spec.rb +48 -0
  36. data/spec/commands/config_spec.rb +45 -0
  37. data/spec/commands/db_spec.rb +53 -0
  38. data/spec/commands/domains_spec.rb +31 -0
  39. data/spec/commands/keys_spec.rb +60 -0
  40. data/spec/commands/logs_spec.rb +21 -0
  41. data/spec/commands/maintenance_spec.rb +21 -0
  42. data/spec/commands/plugins_spec.rb +26 -0
  43. data/spec/commands/ps_spec.rb +16 -0
  44. data/spec/commands/sharing_spec.rb +32 -0
  45. data/spec/commands/ssl_spec.rb +25 -0
  46. data/spec/plugin_spec.rb +64 -0
  47. metadata +150 -0
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'fileutils'
4
+
5
+ require File.dirname(__FILE__) + '/../lib/heroku'
6
+ require 'command'
7
+ require 'commands/base'
8
+ Dir["#{File.dirname(__FILE__)}/../lib/heroku/commands/*"].each { |c| require c }
9
+
10
+ def prepare_command(klass)
11
+ command = klass.new(['--app', 'myapp'])
12
+ command.stub!(:args).and_return([])
13
+ command.stub!(:display)
14
+ command.stub!(:heroku).and_return(mock('heroku client', :host => 'heroku.com'))
15
+ command.stub!(:extract_app).and_return('myapp')
16
+ command
17
+ end
18
+
19
+ module SandboxHelper
20
+ def bash(cmd)
21
+ FileUtils.cd(@sandbox) { |d| return `#{cmd}` }
22
+ end
23
+ end
@@ -0,0 +1,366 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe Heroku::Client do
4
+ before do
5
+ @client = Heroku::Client.new(nil, nil)
6
+ @resource = mock('heroku rest resource')
7
+ @client.stub!(:extract_warning)
8
+ end
9
+
10
+ it "list -> get a list of this user's apps" do
11
+ @client.should_receive(:resource).with('/apps').and_return(@resource)
12
+ @resource.should_receive(:get).and_return <<EOXML
13
+ <?xml version="1.0" encoding="UTF-8"?>
14
+ <apps type="array">
15
+ <app><name>myapp1</name><owner>test@heroku.com</owner></app>
16
+ <app><name>myapp2</name><owner>test@heroku.com</owner></app>
17
+ </apps>
18
+ EOXML
19
+ @client.list.should == [
20
+ ["myapp1", "test@heroku.com"],
21
+ ["myapp2", "test@heroku.com"]
22
+ ]
23
+ end
24
+
25
+ it "info -> get app attributes" do
26
+ @client.should_receive(:resource).with('/apps/myapp').and_return(@resource)
27
+ @resource.should_receive(:get).and_return <<EOXML
28
+ <?xml version='1.0' encoding='UTF-8'?>
29
+ <app>
30
+ <blessed type='boolean'>true</blessed>
31
+ <created-at type='datetime'>2008-07-08T17:21:50-07:00</created-at>
32
+ <id type='integer'>49134</id>
33
+ <name>testgems</name>
34
+ <production type='boolean'>true</production>
35
+ <share-public type='boolean'>true</share-public>
36
+ <domain_name/>
37
+ </app>
38
+ EOXML
39
+ @client.stub!(:list_collaborators).and_return([:jon, :mike])
40
+ @client.stub!(:installed_addons).and_return([:addon1])
41
+ @client.info('myapp').should == { :blessed => 'true', :created_at => '2008-07-08T17:21:50-07:00', :id => '49134', :name => 'testgems', :production => 'true', :share_public => 'true', :domain_name => nil, :collaborators => [:jon, :mike], :addons => [:addon1] }
42
+ end
43
+
44
+ it "create -> create a new blank app" do
45
+ @client.should_receive(:resource).with('/apps').and_return(@resource)
46
+ @resource.should_receive(:post).and_return <<EOXML
47
+ <?xml version="1.0" encoding="UTF-8"?>
48
+ <app><name>untitled-123</name></app>
49
+ EOXML
50
+ @client.create.should == "untitled-123"
51
+ end
52
+
53
+ it "create(name) -> create a new blank app with a specified name" do
54
+ @client.should_receive(:resource).with('/apps').and_return(@resource)
55
+ @resource.should_receive(:post).with({ :app => { :name => 'newapp' } }, @client.heroku_headers).and_return <<EOXML
56
+ <?xml version="1.0" encoding="UTF-8"?>
57
+ <app><name>newapp</name></app>
58
+ EOXML
59
+ @client.create("newapp").should == "newapp"
60
+ end
61
+
62
+ it "update(name, attributes) -> updates existing apps" do
63
+ @client.should_receive(:resource).with('/apps/myapp').and_return(@resource)
64
+ @resource.should_receive(:put).with({ :app => { :mode => 'production', :public => true } }, anything)
65
+ @client.update("myapp", :mode => 'production', :public => true)
66
+ end
67
+
68
+ it "destroy(name) -> destroy the named app" do
69
+ @client.should_receive(:resource).with('/apps/destroyme').and_return(@resource)
70
+ @resource.should_receive(:delete)
71
+ @client.destroy("destroyme")
72
+ end
73
+
74
+ it "rake(app_name, cmd) -> run a rake command on the app" do
75
+ @client.should_receive(:resource).with('/apps/myapp/services').and_return(@resource)
76
+ @resource.should_receive(:post).with('rake db:migrate', @client.heroku_headers.merge(:content_type => 'text/plain'))
77
+ @client.rake('myapp', 'db:migrate')
78
+ end
79
+
80
+ it "console(app_name, cmd) -> run a console command on the app" do
81
+ @client.should_receive(:resource).with('/apps/myapp/console').and_return(@resource)
82
+ @resource.should_receive(:post).with('2+2', @client.heroku_headers)
83
+ @client.console('myapp', '2+2')
84
+ end
85
+
86
+ it "console(app_name) { |c| } -> opens a console session, yields one accessor and closes it after the block" do
87
+ @resources = %w( open run close ).inject({}) { |h, r| h[r] = mock("resource for console #{r}"); h }
88
+ @client.should_receive(:resource).with('/apps/myapp/consoles').and_return(@resources['open'])
89
+ @client.should_receive(:resource).with('/apps/myapp/consoles/42/command').and_return(@resources['run'])
90
+ @client.should_receive(:resource).with('/apps/myapp/consoles/42').and_return(@resources['close'])
91
+ @resources['open'].should_receive(:post).and_return(42)
92
+ @resources['run'].should_receive(:post).with("1+1", anything).and_return('2')
93
+ @resources['close'].should_receive(:delete)
94
+
95
+ @client.console('myapp') do |c|
96
+ c.run("1+1").should == '=> 2'
97
+ end
98
+ end
99
+
100
+ it "restart(app_name) -> restarts the app servers" do
101
+ @client.should_receive(:resource).with('/apps/myapp/server').and_return(@resource)
102
+ @resource.should_receive(:delete).with(anything)
103
+ @client.restart('myapp')
104
+ end
105
+
106
+ it "logs(app_name) -> returns recent output of the app logs" do
107
+ @client.should_receive(:resource).with('/apps/myapp/logs').and_return(@resource)
108
+ @resource.should_receive(:get).and_return('log')
109
+ @client.logs('myapp').should == 'log'
110
+ end
111
+
112
+ it "cron_logs(app_name) -> returns recent output of the app logs" do
113
+ @client.should_receive(:resource).with('/apps/myapp/cron_logs').and_return(@resource)
114
+ @resource.should_receive(:get).and_return('cron log')
115
+ @client.cron_logs('myapp').should == 'cron log'
116
+ end
117
+
118
+ it "set_dynos(app_name, qty) -> scales the app" do
119
+ @client.should_receive(:resource).with('/apps/myapp/dynos').and_return(@resource)
120
+ @resource.should_receive(:put).with({ :dynos => 3 }, anything)
121
+ @client.set_dynos('myapp', 3)
122
+ end
123
+
124
+ it "rake catches 502s and shows the app crashlog" do
125
+ e = RestClient::RequestFailed.new
126
+ e.stub!(:http_code).and_return(502)
127
+ e.stub!(:http_body).and_return('the crashlog')
128
+ @client.should_receive(:post).and_raise(e)
129
+ lambda { @client.rake('myapp', '') }.should raise_error(Heroku::Client::AppCrashed)
130
+ end
131
+
132
+ it "rake passes other status codes (i.e., 500) as standard restclient exceptions" do
133
+ e = RestClient::RequestFailed.new
134
+ e.stub!(:http_code).and_return(500)
135
+ e.stub!(:http_body).and_return('not a crashlog')
136
+ @client.should_receive(:post).and_raise(e)
137
+ lambda { @client.rake('myapp', '') }.should raise_error(RestClient::RequestFailed)
138
+ end
139
+
140
+ describe "bundles" do
141
+ it "gives a temporary URL where the bundle can be downloaded" do
142
+ @client.should_receive(:get).with("/apps/myapp/bundles/latest", {:accept=>"application/json"}).and_return("{\"name\":\"bundle1\",\"temporary_url\":\"https:\\/\\/s3.amazonaws.com\\/herokubundles\\/123.tar.gz\"}")
143
+ @client.bundle_url('myapp').should == 'https://s3.amazonaws.com/herokubundles/123.tar.gz'
144
+ end
145
+ end
146
+
147
+ describe "collaborators" do
148
+ it "list(app_name) -> list app collaborators" do
149
+ @client.should_receive(:resource).with('/apps/myapp/collaborators').and_return(@resource)
150
+ @resource.should_receive(:get).and_return <<EOXML
151
+ <?xml version="1.0" encoding="UTF-8"?>
152
+ <collaborators type="array">
153
+ <collaborator><email>joe@example.com</email></collaborator>
154
+ <collaborator><email>jon@example.com</email></collaborator>
155
+ </collaborators>
156
+ EOXML
157
+ @client.list_collaborators('myapp').should == [
158
+ { :email => 'joe@example.com' },
159
+ { :email => 'jon@example.com' }
160
+ ]
161
+ end
162
+
163
+ it "add_collaborator(app_name, email) -> adds collaborator to app" do
164
+ @client.should_receive(:resource).with('/apps/myapp/collaborators').and_return(@resource)
165
+ @resource.should_receive(:post).with({ 'collaborator[email]' => 'joe@example.com'}, anything)
166
+ @client.add_collaborator('myapp', 'joe@example.com')
167
+ end
168
+
169
+ it "remove_collaborator(app_name, email) -> removes collaborator from app" do
170
+ @client.should_receive(:resource).with('/apps/myapp/collaborators/joe%40example%2Ecom').and_return(@resource)
171
+ @resource.should_receive(:delete)
172
+ @client.remove_collaborator('myapp', 'joe@example.com')
173
+ end
174
+ end
175
+
176
+ describe "domain names" do
177
+ it "list(app_name) -> list app domain names" do
178
+ @client.should_receive(:resource).with('/apps/myapp/domains').and_return(@resource)
179
+ @resource.should_receive(:get).and_return <<EOXML
180
+ <?xml version="1.0" encoding="UTF-8"?>
181
+ <domain-names type="array">
182
+ <domain-name><domain>example1.com</domain></domain-name>
183
+ <domain-name><domain>example2.com</domain></domain-name>
184
+ </domain-names>
185
+ EOXML
186
+ @client.list_domains('myapp').should == [{:domain => 'example1.com'}, {:domain => 'example2.com'}]
187
+ end
188
+
189
+ it "add_domain(app_name, domain) -> adds domain name to app" do
190
+ @client.should_receive(:resource).with('/apps/myapp/domains').and_return(@resource)
191
+ @resource.should_receive(:post).with('example.com', anything)
192
+ @client.add_domain('myapp', 'example.com')
193
+ end
194
+
195
+ it "remove_domain(app_name, domain) -> removes domain name from app" do
196
+ @client.should_receive(:resource).with('/apps/myapp/domains/example.com').and_return(@resource)
197
+ @resource.should_receive(:delete)
198
+ @client.remove_domain('myapp', 'example.com')
199
+ end
200
+
201
+ it "remove_domains(app_name) -> removes all domain names from app" do
202
+ @client.should_receive(:resource).with('/apps/myapp/domains').and_return(@resource)
203
+ @resource.should_receive(:delete)
204
+ @client.remove_domains('myapp')
205
+ end
206
+
207
+ it "add_ssl(app_name, pem, key) -> adds a ssl cert to the domain" do
208
+ @client.should_receive(:resource).with('/apps/myapp/ssl').and_return(@resource)
209
+ @resource.should_receive(:post).with({ :pem => 'pem', :key => 'key' }, anything).and_return('{}')
210
+ @client.add_ssl('myapp', 'pem', 'key')
211
+ end
212
+
213
+ it "remove_ssl(app_name, domain) -> removes the ssl cert for the domain" do
214
+ @client.should_receive(:resource).with('/apps/myapp/domains/example.com/ssl').and_return(@resource)
215
+ @resource.should_receive(:delete)
216
+ @client.remove_ssl('myapp', 'example.com')
217
+ end
218
+ end
219
+
220
+ describe "ssh keys" do
221
+ it "fetches a list of the user's current keys" do
222
+ @client.should_receive(:resource).with('/user/keys').and_return(@resource)
223
+ @resource.should_receive(:get).and_return <<EOXML
224
+ <?xml version="1.0" encoding="UTF-8"?>
225
+ <keys type="array">
226
+ <key>
227
+ <contents>ssh-dss thekey== joe@workstation</contents>
228
+ </key>
229
+ </keys>
230
+ EOXML
231
+ @client.keys.should == [ "ssh-dss thekey== joe@workstation" ]
232
+ end
233
+
234
+ it "add_key(key) -> add an ssh key (e.g., the contents of id_rsa.pub) to the user" do
235
+ @client.should_receive(:resource).with('/user/keys').and_return(@resource)
236
+ @client.stub!(:heroku_headers).and_return({})
237
+ @resource.should_receive(:post).with('a key', 'Content-Type' => 'text/ssh-authkey')
238
+ @client.add_key('a key')
239
+ end
240
+
241
+ it "remove_key(key) -> remove an ssh key by name (user@box)" do
242
+ @client.should_receive(:resource).with('/user/keys/joe%40workstation').and_return(@resource)
243
+ @resource.should_receive(:delete)
244
+ @client.remove_key('joe@workstation')
245
+ end
246
+
247
+ it "remove_all_keys -> removes all ssh keys for the user" do
248
+ @client.should_receive(:resource).with('/user/keys').and_return(@resource)
249
+ @resource.should_receive(:delete)
250
+ @client.remove_all_keys
251
+ end
252
+
253
+ it "database_session(app_name) -> creates a taps database session" do
254
+ @client.should_receive(:resource).with('/apps/myapp/database/session').and_return(@resource)
255
+ @resource.should_receive(:post).with('', anything)
256
+ @client.database_session('myapp')
257
+ end
258
+
259
+ it "database_reset(app_name) -> reset an app's database" do
260
+ @client.should_receive(:resource).with('/apps/myapp/database/reset').and_return(@resource)
261
+ @resource.should_receive(:post).with('', anything)
262
+ @client.database_reset('myapp')
263
+ end
264
+
265
+ it "maintenance(app_name, :on) -> sets maintenance mode for an app" do
266
+ @client.should_receive(:resource).with('/apps/myapp/server/maintenance').and_return(@resource)
267
+ @resource.should_receive(:post).with({:maintenance_mode => '1'}, anything)
268
+ @client.maintenance('myapp', :on)
269
+ end
270
+
271
+ it "maintenance(app_name, :off) -> turns off maintenance mode for an app" do
272
+ @client.should_receive(:resource).with('/apps/myapp/server/maintenance').and_return(@resource)
273
+ @resource.should_receive(:post).with({:maintenance_mode => '0'}, anything)
274
+ @client.maintenance('myapp', :off)
275
+ end
276
+ end
277
+
278
+ describe "config vars" do
279
+ it "config_vars(app_name) -> json hash of config vars for the app" do
280
+ @client.should_receive(:resource).with('/apps/myapp/config_vars').and_return(@resource)
281
+ @resource.should_receive(:get).and_return '{"A":"one", "B":"two"}'
282
+ @client.config_vars('myapp').should == { 'A' => 'one', 'B' => 'two'}
283
+ end
284
+
285
+ it "add_config_vars(app_name, vars)" do
286
+ @client.should_receive(:resource).with('/apps/myapp/config_vars').and_return(@resource)
287
+ @resource.should_receive(:put).with('{"x":"y"}', anything)
288
+ @client.add_config_vars('myapp', {:x => 'y'})
289
+ end
290
+
291
+ it "remove_config_var(app_name, key)" do
292
+ @client.should_receive(:resource).with('/apps/myapp/config_vars/mykey').and_return(@resource)
293
+ @resource.should_receive(:delete)
294
+ @client.remove_config_var('myapp', 'mykey')
295
+ end
296
+
297
+ it "clear_config_vars(app_name) -> resets all config vars for this app" do
298
+ @client.should_receive(:resource).with('/apps/myapp/config_vars').and_return(@resource)
299
+ @resource.should_receive(:delete)
300
+ @client.clear_config_vars('myapp')
301
+ end
302
+ end
303
+
304
+ describe "addons" do
305
+ it "addons -> array with addons available for installation" do
306
+ @client.should_receive(:resource).with('/addons').and_return(@resource)
307
+ @resource.should_receive(:get).and_return '[{"name":"addon1"}, {"name":"addon2"}]'
308
+ @client.addons.should == [{'name' => 'addon1'}, {'name' => 'addon2'}]
309
+ end
310
+
311
+ it "installed_addons(app_name) -> array of installed addons" do
312
+ @client.should_receive(:resource).with('/apps/myapp/addons').and_return(@resource)
313
+ @resource.should_receive(:get).and_return '[{"name":"addon1"}]'
314
+ @client.installed_addons('myapp').should == [{'name' => 'addon1'}]
315
+ end
316
+
317
+ it "install_addon(app_name, addon_name)" do
318
+ @client.should_receive(:resource).with('/apps/myapp/addons/addon1').and_return(@resource)
319
+ @resource.should_receive(:post)
320
+ @client.install_addon('myapp', 'addon1')
321
+ end
322
+
323
+ it "uninstall_addon(app_name, addon_name)" do
324
+ @client.should_receive(:resource).with('/apps/myapp/addons/addon1').and_return(@resource)
325
+ @resource.should_receive(:delete)
326
+ @client.uninstall_addon('myapp', 'addon1')
327
+ end
328
+ end
329
+
330
+ describe "internal" do
331
+ before do
332
+ @client = Heroku::Client.new(nil, nil)
333
+ end
334
+
335
+ it "creates a RestClient resource for making calls" do
336
+ @client.stub!(:host).and_return('heroku.com')
337
+ @client.stub!(:user).and_return('joe@example.com')
338
+ @client.stub!(:password).and_return('secret')
339
+
340
+ res = @client.resource('/xyz')
341
+
342
+ res.url.should == 'https://api.heroku.com/xyz'
343
+ res.user.should == 'joe@example.com'
344
+ res.password.should == 'secret'
345
+ end
346
+
347
+ it "runs a callback when the API sets a warning header" do
348
+ response = mock('rest client response', :headers => { :x_heroku_warning => 'Warning' })
349
+ @client.should_receive(:resource).with('test').and_return(@resource)
350
+ @resource.should_receive(:get).and_return(response)
351
+ @client.on_warning { |msg| @callback = msg }
352
+ @client.get('test')
353
+ @callback.should == 'Warning'
354
+ end
355
+
356
+ it "doesn't run the callback twice for the same warning" do
357
+ response = mock('rest client response', :headers => { :x_heroku_warning => 'Warning' })
358
+ @client.stub!(:resource).and_return(@resource)
359
+ @resource.stub!(:get).and_return(response)
360
+ @client.on_warning { |msg| @callback_called ||= 0; @callback_called += 1 }
361
+ @client.get('test1')
362
+ @client.get('test2')
363
+ @callback_called.should == 1
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe Heroku::Command do
4
+ it "extracts error messages from response when available in XML" do
5
+ Heroku::Command.extract_error('<errors><error>Invalid app name</error></errors>').should == ' ! Invalid app name'
6
+ end
7
+
8
+ it "extracts error messages from response when available in JSON" do
9
+ Heroku::Command.extract_error("{\"error\":\"Invalid app name\"}").should == ' ! Invalid app name'
10
+ end
11
+
12
+ it "shows Internal Server Error when the response doesn't contain a XML" do
13
+ Heroku::Command.extract_error('<h1>HTTP 500</h1>').should == ' ! Internal server error'
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/../base'
2
+
3
+ module Heroku::Command
4
+ describe Addons do
5
+ before do
6
+ @addons = prepare_command(Addons)
7
+ end
8
+
9
+ it "index lists installed addons" do
10
+ @addons.heroku.should_receive(:installed_addons).with('myapp').and_return([])
11
+ @addons.index
12
+ end
13
+
14
+ it "adds an addon" do
15
+ @addons.stub!(:args).and_return(%w(my_addon))
16
+ @addons.heroku.should_receive(:install_addon).with('myapp', 'my_addon', {})
17
+ @addons.add
18
+ end
19
+
20
+ it "adds an addon with config vars" do
21
+ @addons.stub!(:args).and_return(%w(my_addon foo=baz))
22
+ @addons.heroku.should_receive(:install_addon).with('myapp', 'my_addon', { 'foo' => 'baz' })
23
+ @addons.add
24
+ end
25
+
26
+ it "asks the user to confirm billing when API responds with 402" do
27
+ @addons.stub!(:args).and_return(%w( addon1 ))
28
+ e = RestClient::RequestFailed.new
29
+ e.stub!(:http_code).and_return(402)
30
+ @addons.heroku.should_receive(:install_addon).and_raise(e)
31
+ @addons.should_receive(:confirm_billing).and_return(false)
32
+ @addons.add
33
+ end
34
+
35
+ it "removes addons" do
36
+ @addons.stub!(:args).and_return(%w( addon1 ))
37
+ @addons.heroku.should_receive(:uninstall_addon).with('myapp', 'addon1')
38
+ @addons.remove
39
+ end
40
+
41
+ it "clears addons" do
42
+ @addons.heroku.should_receive(:installed_addons).with('myapp').and_return([{ 'name' => 'addon1' }])
43
+ @addons.heroku.should_receive(:uninstall_addon).with('myapp', 'addon1')
44
+ @addons.clear
45
+ end
46
+ end
47
+ end