sauce_whisk 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OGZmYWI0OTdkOGZhN2Y4MTJlOGI3NmIwZDkwODA4MTBhZmViOTlmYg==
4
+ NDgyMTEzNDI3YWYzZTg2ZDBlODEwYWEwNjE3ODBjMTgzMDNlZmUzMQ==
5
5
  data.tar.gz: !binary |-
6
- MTMzNDg5Y2Q5YjdjNjlkMWIwZDNkZTBjNDc2Y2FhYzhmODIxZmQ4MA==
6
+ MzM0YWI4OGNjMGU4YTJkZGMzMTE2ZWE0YWRmYzVmZmQ4ODk2ODMxOQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- ZmVlZjYzMDY5YjUwYzE2ZDBjMTc5OGZhN2Y1Njk3MjUyODk2NTgyZDBkM2Nk
10
- ZjdlYTBiODc1OTFkNGVmOGYxNGYyMjBiZmE4ODVjOWFhOTMxMjE0MjQ4MGE3
11
- MWM5YWE5MTU5NGEyOWU3ODNhZTA1NDUyNGZmYmZhYWRmNDQ3Nzc=
9
+ NzM4YjkzNDMzMTM1OGFiZTFhZDk3YmM3ZjdiZGUxNGVkMDE1NzdhODhhNGYx
10
+ NzlkMDI2MTBhMjEwZGU2NzAwYTk1OGI4NDhiOWRlYWViMTMyNDMwOGJjOTJh
11
+ M2ZlZDM5YWM1ZWZiZjczZTNjOTA2MjQwZjY0ZDdiOTY1NTU1Mjc=
12
12
  data.tar.gz: !binary |-
13
- NWUwYjk5MjJjYzE0MGY4MzliODhhZGIwMGZhZThlYzNiYTEzZmY1ZTQxMjM2
14
- OGE1MDE2MGNiZjIyYzk5NzNlMjA2OGEzMDhkYjA4NGY0YzNiZTk4Y2IwN2Y0
15
- OTEwMDI1N2Q2MGM5ZWEyYjFjYzQzMTg3OGI1ZTU2MTU3NTVhOTU=
13
+ MmUyMWU5MjE1YWJiOWJkN2Y0YTlhY2Y0YjdlMjU1YjRkZTczNDM4OGZlYTk0
14
+ MTliNWI2M2EyMGM1NGYyOGQ4Y2VlOWUxM2FjMzU4YWI0MDBhM2VlZmRhNjNl
15
+ Y2I3ZDA2YjM0YzFmYzJlMTgzOTg1NDU3MWY0M2I1YzNjNGM2MDI=
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- group :test do
4
- gem 'psych', "2.0.0", :path => "./vendor/psych"
5
- end
3
+ #group :test do
4
+ # gem 'psych', "2.0.0", :path => "./vendor/psych"
5
+ #end
6
6
 
7
7
  gemspec
data/README.md CHANGED
@@ -35,6 +35,7 @@ SAUCE_ACCESS_KEY=Your Access Key, found on the lower left of your Account page
35
35
  ```ruby
36
36
  SauceWhisk::Jobs.pass_job job_id
37
37
  SauceWhisk::Jobs.fail_job job_id
38
+ SauceWhisk::Jobs.change_status job_id, true_for_passed_false_for_failed
38
39
  ```
39
40
 
40
41
  ### Creating Job Objects
@@ -0,0 +1,3 @@
1
+ # Major Version 0
2
+ ### 0.0.4
3
+ * Added configuration loading from the Sauce gem, if present
@@ -45,6 +45,10 @@ module SauceWhisk
45
45
  Job.new(job_hash)
46
46
  end
47
47
 
48
+ def self.stop(job_id)
49
+ put "#{job_id}/stop", {}
50
+ end
51
+
48
52
  def self.fetch_asset(job_id, asset)
49
53
  asset = get "#{job_id}/assets/#{asset}"
50
54
  end
@@ -91,6 +95,10 @@ module SauceWhisk
91
95
  Jobs.save(self)
92
96
  end
93
97
 
98
+ def stop
99
+ Jobs.stop id
100
+ end
101
+
94
102
  def updated_fields
95
103
  @updated_fields ||= []
96
104
  end
@@ -7,7 +7,7 @@ module SauceWhisk
7
7
  RestClient::Request.execute({:method => :get, :url => resource_url}.merge auth_details)
8
8
  end
9
9
 
10
- def put(resource_id, body)
10
+ def put(resource_id, body={})
11
11
  url = "#{fully_qualified_resource}/#{resource_id}"
12
12
  length = body.length
13
13
  headers = {"Content-Length" => length}
@@ -15,11 +15,17 @@ module SauceWhisk
15
15
  :method => :put,
16
16
  :url => url,
17
17
  :payload => body,
18
- :content_type => "application/json"
18
+ :content_type => "application/json",
19
+ :headers => headers
19
20
  }
20
21
  RestClient::Request.execute(req_params.merge auth_details)
21
22
  end
22
23
 
24
+ def delete(resource_id)
25
+ resource_to_delete = fully_qualified_resource << "/#{resource_id}"
26
+ RestClient::Request.execute({:method => :delete, :url => resource_to_delete}.merge auth_details)
27
+ end
28
+
23
29
  def auth_details
24
30
  {:user => SauceWhisk.username, :password => SauceWhisk.password}
25
31
  end
@@ -0,0 +1,48 @@
1
+ require 'sauce_whisk/rest_request_builder'
2
+
3
+ module SauceWhisk
4
+ class Tunnels
5
+ extend RestRequestBuilder
6
+
7
+ def self.resource
8
+ "#{SauceWhisk.username}/tunnels"
9
+ end
10
+
11
+ def self.all(fetch_each = false)
12
+ all_tunnels = JSON.parse get
13
+
14
+ unless fetch_each
15
+ return all_tunnels
16
+ end
17
+
18
+ tunnels = all_tunnels.map do |tunnel|
19
+ fetch tunnel
20
+ end
21
+
22
+ return tunnels
23
+ end
24
+
25
+ def self.stop tunnel_id
26
+ delete tunnel_id
27
+ end
28
+
29
+ def self.fetch tunnel_id
30
+ tunnel_parameters = JSON.parse get tunnel_id
31
+ Tunnel.new tunnel_parameters
32
+ end
33
+ end
34
+
35
+ class Tunnel
36
+ attr_reader :id, :owner, :status, :host, :creation_time
37
+
38
+ def initialize(params)
39
+ params.each do |param, val|
40
+ self.instance_variable_set("@#{param}", val)
41
+ end
42
+ end
43
+
44
+ def stop
45
+ SauceWhisk::Tunnels.stop self.id
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module SauceWhisk
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/sauce_whisk.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "sauce_whisk/version"
2
2
  require "sauce_whisk/jobs"
3
3
  require "sauce_whisk/assets"
4
+ require "sauce_whisk/tunnels"
4
5
  require "sauce_whisk/rest_request_builder"
5
6
 
6
7
  module SauceWhisk
@@ -10,11 +11,19 @@ module SauceWhisk
10
11
  end
11
12
 
12
13
  def self.username
13
- ENV["SAUCE_USERNAME"]
14
+ if defined? Sauce
15
+ return Sauce.get_config.username
16
+ else
17
+ return ENV["SAUCE_USERNAME"]
18
+ end
14
19
  end
15
20
 
16
21
  def self.password
17
- ENV["SAUCE_ACCESS_KEY"]
22
+ if defined? Sauce
23
+ return Sauce.get_config.password
24
+ else
25
+ return ENV["SAUCE_ACCESS_KEY"]
26
+ end
18
27
  end
19
28
 
20
29
  def self.pass_job(job_id)
@@ -8,7 +8,7 @@ http_interactions:
8
8
  string: ''
9
9
  headers:
10
10
  Accept:
11
- - ! '*/*; q=0.5, application/xml'
11
+ - '*/*; q=0.5, application/xml'
12
12
  Accept-Encoding:
13
13
  - gzip, deflate
14
14
  User-Agent:
@@ -16,44 +16,31 @@ http_interactions:
16
16
  response:
17
17
  status:
18
18
  code: 200
19
- message: !binary |-
20
- T0s=
19
+ message: OK
21
20
  headers:
22
- !binary "U2VydmVy":
23
- - !binary |-
24
- bmdpbngvMC43LjYy
25
- !binary "RGF0ZQ==":
26
- - !binary |-
27
- RnJpLCAyNCBNYXkgMjAxMyAwNjowNjozOSBHTVQ=
28
- !binary "Q29udGVudC1UeXBl":
29
- - !binary |-
30
- YXBwbGljYXRpb24vanNvbg==
31
- !binary "VHJhbnNmZXItRW5jb2Rpbmc=":
32
- - !binary |-
33
- Y2h1bmtlZA==
34
- !binary "Q29ubmVjdGlvbg==":
35
- - !binary |-
36
- a2VlcC1hbGl2ZQ==
37
- !binary "UHJhZ21h":
38
- - !binary |-
39
- bm8tY2FjaGU=
40
- !binary "Q2FjaGUtQ29udHJvbA==":
41
- - !binary |-
42
- bm8tY2FjaGU=
43
- !binary "WC1GcmFtZS1PcHRpb25z":
44
- - !binary |-
45
- REVOWQ==
46
- !binary "U2V0LUNvb2tpZQ==":
47
- - !binary |-
48
- Y3NyZl90b2tlbj01M2I2Nzc4MDk4NjdkZmMyZGRkNjU4MjI4YzYzYTc2Njsg
49
- ZXhwaXJlcz0iRnJpLCAyNC1NYXktMjAxMyAwNjoyMzoxOSBHTVQiOyBNYXgt
50
- QWdlPTYwMDsgUGF0aD0v
51
- - !binary |-
52
- bWV0cmljcz0zMTNlYzg5ZmFlMzhmNDQwOGI3MzY1ODEzZjI1MGMyMzk2YWIw
53
- Nzk2ZGZmZjI3ODhkODY3NDcyYmJjM2Q4YmIzOTRiMDRjYzg7IFBhdGg9Lw==
54
- !binary "Q29udGVudC1FbmNvZGluZw==":
55
- - !binary |-
56
- Z3ppcA==
21
+ Server:
22
+ - nginx/0.7.62
23
+ Date:
24
+ - Fri, 24 May 2013 06:06:39 GMT
25
+ Content-Type:
26
+ - application/json
27
+ Transfer-Encoding:
28
+ - chunked
29
+ Connection:
30
+ - keep-alive
31
+ Pragma:
32
+ - no-cache
33
+ Cache-Control:
34
+ - no-cache
35
+ X-Frame-Options:
36
+ - DENY
37
+ Set-Cookie:
38
+ - csrf_token=53b677809867dfc2ddd658228c63a766; expires="Fri, 24-May-2013 06:23:19
39
+ GMT"; Max-Age=600; Path=/
40
+ - metrics=313ec89fae38f4408b7365813f250c2396ab0796dfff2788d867472bbc3d8bb394b04cc8;
41
+ Path=/
42
+ Content-Encoding:
43
+ - gzip
57
44
  body:
58
45
  encoding: ASCII-8BIT
59
46
  string: !binary |-
@@ -110,10 +97,10 @@ http_interactions:
110
97
  uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/jobs/bd9c43dd6b5549f1b942d1d581d98cac
111
98
  body:
112
99
  encoding: UTF-8
113
- string: ! '{"passed":true}'
100
+ string: '{"passed":true}'
114
101
  headers:
115
102
  Accept:
116
- - ! '*/*; q=0.5, application/xml'
103
+ - '*/*; q=0.5, application/xml'
117
104
  Accept-Encoding:
118
105
  - gzip, deflate
119
106
  Content-Length:
@@ -148,7 +135,7 @@ http_interactions:
148
135
  - '611'
149
136
  body:
150
137
  encoding: US-ASCII
151
- string: ! '{"status": "complete", "commands_not_successful": 2, "name": "Ruby
138
+ string: '{"status": "complete", "commands_not_successful": 2, "name": "Ruby
152
139
  Example for Appium", "video_url": "http://saucelabs.com/jobs/bd9c43dd6b5549f1b942d1d581d98cac/video.flv",
153
140
  "tags": [], "proxied": false, "start_time": 1362100867, "log_url": "http://saucelabs.com/jobs/bd9c43dd6b5549f1b942d1d581d98cac/selenium-server.log",
154
141
  "creation_time": 1362100864, "custom-data": null, "public": null, "browser_version":
@@ -165,7 +152,7 @@ http_interactions:
165
152
  string: ''
166
153
  headers:
167
154
  Accept:
168
- - ! '*/*; q=0.5, application/xml'
155
+ - '*/*; q=0.5, application/xml'
169
156
  Accept-Encoding:
170
157
  - gzip, deflate
171
158
  User-Agent:
@@ -198,7 +185,7 @@ http_interactions:
198
185
  - '611'
199
186
  body:
200
187
  encoding: US-ASCII
201
- string: ! '{"status": "complete", "commands_not_successful": 2, "name": "Ruby
188
+ string: '{"status": "complete", "commands_not_successful": 2, "name": "Ruby
202
189
  Example for Appium", "video_url": "http://saucelabs.com/jobs/bd9c43dd6b5549f1b942d1d581d98cac/video.flv",
203
190
  "tags": [], "proxied": false, "start_time": 1362100867, "log_url": "http://saucelabs.com/jobs/bd9c43dd6b5549f1b942d1d581d98cac/selenium-server.log",
204
191
  "creation_time": 1362100864, "custom-data": null, "public": null, "browser_version":
@@ -215,7 +202,7 @@ http_interactions:
215
202
  string: ''
216
203
  headers:
217
204
  Accept:
218
- - ! '*/*; q=0.5, application/xml'
205
+ - '*/*; q=0.5, application/xml'
219
206
  Accept-Encoding:
220
207
  - gzip, deflate
221
208
  User-Agent:
@@ -248,9 +235,267 @@ http_interactions:
248
235
  - '191'
249
236
  body:
250
237
  encoding: US-ASCII
251
- string: ! '{"sauce-log": "log.json", "video": "video.flv", "selenium-log": "selenium-server.log",
238
+ string: '{"sauce-log": "log.json", "video": "video.flv", "selenium-log": "selenium-server.log",
252
239
  "screenshots": ["0000screenshot.png", "0001screenshot.png", "0002screenshot.png",
253
240
  "0003screenshot.png"]}'
254
241
  http_version:
255
242
  recorded_at: Fri, 24 May 2013 06:13:26 GMT
243
+ - request:
244
+ method: put
245
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/jobs/job_id/stop
246
+ body:
247
+ encoding: ASCII-8BIT
248
+ string: ''
249
+ headers:
250
+ Accept:
251
+ - '*/*; q=0.5, application/xml'
252
+ Accept-Encoding:
253
+ - gzip, deflate
254
+ Content-Length:
255
+ - '0'
256
+ Content-Type:
257
+ - application/x-www-form-urlencoded
258
+ User-Agent:
259
+ - Ruby
260
+ response:
261
+ status:
262
+ code: 404
263
+ message: Not Found
264
+ headers:
265
+ Server:
266
+ - nginx/0.7.62
267
+ Date:
268
+ - Mon, 10 Jun 2013 07:14:49 GMT
269
+ Content-Type:
270
+ - application/json
271
+ Connection:
272
+ - keep-alive
273
+ Pragma:
274
+ - no-cache
275
+ Cache-Control:
276
+ - no-cache
277
+ X-Frame-Options:
278
+ - DENY
279
+ Set-Cookie:
280
+ - csrf_token=4298b6049c24168640f07d791dcf5b65; expires="Mon, 10-Jun-2013 07:31:53
281
+ GMT"; Max-Age=600; Path=/
282
+ - metrics=b0b8f1841b3c8208349be86ea61357059b0e4ed4c26ca98213b74728815a4ec47d432151;
283
+ Path=/
284
+ - metrics=f36ba36dd62295ed19dfa7023109af257d3744e6901106f8b3254c599af08d386c3a857e;
285
+ Path=/
286
+ Content-Length:
287
+ - '23'
288
+ body:
289
+ encoding: US-ASCII
290
+ string: '{"errors": "Not Found"}'
291
+ http_version:
292
+ recorded_at: Mon, 10 Jun 2013 07:21:54 GMT
293
+ - request:
294
+ method: put
295
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/jobs/986bafd435c341b7bf8a1ee995606183/stop
296
+ body:
297
+ encoding: ASCII-8BIT
298
+ string: ''
299
+ headers:
300
+ Accept:
301
+ - '*/*; q=0.5, application/xml'
302
+ Accept-Encoding:
303
+ - gzip, deflate
304
+ Content-Length:
305
+ - '0'
306
+ Content-Type:
307
+ - application/x-www-form-urlencoded
308
+ User-Agent:
309
+ - Ruby
310
+ response:
311
+ status:
312
+ code: 404
313
+ message: Not Found
314
+ headers:
315
+ Server:
316
+ - nginx/0.7.62
317
+ Date:
318
+ - Mon, 10 Jun 2013 07:30:03 GMT
319
+ Content-Type:
320
+ - application/json
321
+ Connection:
322
+ - keep-alive
323
+ Pragma:
324
+ - no-cache
325
+ Cache-Control:
326
+ - no-cache
327
+ X-Frame-Options:
328
+ - DENY
329
+ Set-Cookie:
330
+ - csrf_token=6b2be9b2c3e8a2b606b10944adfa767d; expires="Mon, 10-Jun-2013 07:47:08
331
+ GMT"; Max-Age=600; Path=/
332
+ - metrics=8a5fd4768016507bd6281762cf9f4056814fa577ae1b658c62a745078c750aa67afeab47;
333
+ Path=/
334
+ - metrics=c92deb1233a3ae422dbbbdb2ff820a436485cd867ac5a39655234d99aa91fbfed6336887;
335
+ Path=/
336
+ Content-Length:
337
+ - '23'
338
+ body:
339
+ encoding: US-ASCII
340
+ string: '{"errors": "Not Found"}'
341
+ http_version:
342
+ recorded_at: Mon, 10 Jun 2013 07:37:09 GMT
343
+ - request:
344
+ method: put
345
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/jobs/3edc8fe6d52645bf931b1003da65af1f/stop
346
+ body:
347
+ encoding: ASCII-8BIT
348
+ string: ''
349
+ headers:
350
+ Accept:
351
+ - '*/*; q=0.5, application/xml'
352
+ Accept-Encoding:
353
+ - gzip, deflate
354
+ Content-Length:
355
+ - '0'
356
+ Content-Type:
357
+ - application/x-www-form-urlencoded
358
+ User-Agent:
359
+ - Ruby
360
+ response:
361
+ status:
362
+ code: 200
363
+ message: OK
364
+ headers:
365
+ Server:
366
+ - nginx/0.7.62
367
+ Date:
368
+ - Mon, 10 Jun 2013 07:31:41 GMT
369
+ Content-Type:
370
+ - application/json
371
+ Connection:
372
+ - keep-alive
373
+ Pragma:
374
+ - no-cache
375
+ Cache-Control:
376
+ - no-cache
377
+ X-Frame-Options:
378
+ - DENY
379
+ Set-Cookie:
380
+ - csrf_token=35e126296b2dc908d1694a45051b4a4b; expires="Mon, 10-Jun-2013 07:48:45
381
+ GMT"; Max-Age=600; Path=/
382
+ - metrics=ac238c38215972b4e2b21e106220620bcda4daf3f066f7410d2f451dba13b5df645fc89d;
383
+ Path=/
384
+ Content-Length:
385
+ - '652'
386
+ body:
387
+ encoding: US-ASCII
388
+ string: '{"status": "complete", "commands_not_successful": 0, "name": "Manual
389
+ Session at \\011http://www.artindustrial.com", "video_url": "http://saucelabs.com/jobs/3edc8fe6d52645bf931b1003da65af1f/video.flv",
390
+ "tags": [], "proxied": false, "start_time": 1370848961, "log_url": "http://saucelabs.com/jobs/3edc8fe6d52645bf931b1003da65af1f/selenium-server.log",
391
+ "creation_time": 1370848961, "custom-data": null, "public": null, "browser_version":
392
+ "4.0.4", "end_time": 1370849925, "passed": null, "error": "User terminated",
393
+ "owner": "<SAUCE_USERNAME>", "build": null, "os": "Linux", "id": "3edc8fe6d52645bf931b1003da65af1f",
394
+ "breakpointed": null, "browser": "android"}'
395
+ http_version:
396
+ recorded_at: Mon, 10 Jun 2013 07:38:46 GMT
397
+ - request:
398
+ method: put
399
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/jobs/9591ce8519f043f8b3bea0462923c883/stop
400
+ body:
401
+ encoding: ASCII-8BIT
402
+ string: ''
403
+ headers:
404
+ Accept:
405
+ - '*/*; q=0.5, application/xml'
406
+ Accept-Encoding:
407
+ - gzip, deflate
408
+ Content-Length:
409
+ - '0'
410
+ Content-Type:
411
+ - application/x-www-form-urlencoded
412
+ User-Agent:
413
+ - Ruby
414
+ response:
415
+ status:
416
+ code: 200
417
+ message: OK
418
+ headers:
419
+ Server:
420
+ - nginx/0.7.62
421
+ Date:
422
+ - Mon, 10 Jun 2013 07:58:27 GMT
423
+ Content-Type:
424
+ - application/json
425
+ Connection:
426
+ - keep-alive
427
+ Pragma:
428
+ - no-cache
429
+ Cache-Control:
430
+ - no-cache
431
+ X-Frame-Options:
432
+ - DENY
433
+ Set-Cookie:
434
+ - csrf_token=2d1a8d0e7f5eeb39b148d343fbdbf94a; expires="Mon, 10-Jun-2013 08:15:31
435
+ GMT"; Max-Age=600; Path=/
436
+ - metrics=9fe34163bd1c71b54125f30a824b987b8b4293c71041f8b4df454d86a4c89cdf27da352d;
437
+ Path=/
438
+ Content-Length:
439
+ - '619'
440
+ body:
441
+ encoding: US-ASCII
442
+ string: '{"status": "complete", "commands_not_successful": 1, "name": "Wikipedia''s
443
+ Miso Page fails", "video_url": "http://saucelabs.com/jobs/9591ce8519f043f8b3bea0462923c883/video.flv",
444
+ "tags": [], "proxied": true, "start_time": 1370846866, "log_url": "http://saucelabs.com/jobs/9591ce8519f043f8b3bea0462923c883/selenium-server.log",
445
+ "creation_time": 1370846866, "custom-data": null, "public": null, "browser_version":
446
+ "17.0.1", "end_time": 1370846883, "passed": false, "error": null, "owner":
447
+ "<SAUCE_USERNAME>", "build": null, "os": "Mac 10.6", "id": "9591ce8519f043f8b3bea0462923c883",
448
+ "breakpointed": null, "browser": "firefox"}'
449
+ http_version:
450
+ recorded_at: Mon, 10 Jun 2013 08:05:32 GMT
451
+ - request:
452
+ method: put
453
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/jobs/dsfargeg/stop
454
+ body:
455
+ encoding: ASCII-8BIT
456
+ string: ''
457
+ headers:
458
+ Accept:
459
+ - '*/*; q=0.5, application/xml'
460
+ Accept-Encoding:
461
+ - gzip, deflate
462
+ Content-Length:
463
+ - '0'
464
+ Content-Type:
465
+ - application/x-www-form-urlencoded
466
+ User-Agent:
467
+ - Ruby
468
+ response:
469
+ status:
470
+ code: 404
471
+ message: Not Found
472
+ headers:
473
+ Server:
474
+ - nginx/0.7.62
475
+ Date:
476
+ - Mon, 10 Jun 2013 08:47:43 GMT
477
+ Content-Type:
478
+ - application/json
479
+ Connection:
480
+ - keep-alive
481
+ Pragma:
482
+ - no-cache
483
+ Cache-Control:
484
+ - no-cache
485
+ X-Frame-Options:
486
+ - DENY
487
+ Set-Cookie:
488
+ - csrf_token=400011d970af000bde75fc33d3c2fa66; expires="Mon, 10-Jun-2013 09:04:48
489
+ GMT"; Max-Age=600; Path=/
490
+ - metrics=65c699bb137a29a1d8b4dc9250854eb35608035468aa780535534c269393f16a7ea3ca35;
491
+ Path=/
492
+ - metrics=c18c09599c25a7fba270b5c639dc185240c5cb72c92f89a4e9d547d48c6b0079ca4714b3;
493
+ Path=/
494
+ Content-Length:
495
+ - '23'
496
+ body:
497
+ encoding: US-ASCII
498
+ string: '{"errors": "Not Found"}'
499
+ http_version:
500
+ recorded_at: Mon, 10 Jun 2013 08:54:49 GMT
256
501
  recorded_with: VCR 2.5.0
@@ -0,0 +1,47 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/tunnels
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - '*/*; q=0.5, application/xml'
12
+ Accept-Encoding:
13
+ - gzip, deflate
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Server:
22
+ - nginx/0.7.62
23
+ Date:
24
+ - Tue, 11 Jun 2013 04:14:00 GMT
25
+ Content-Type:
26
+ - application/json
27
+ Connection:
28
+ - keep-alive
29
+ Pragma:
30
+ - no-cache
31
+ Cache-Control:
32
+ - no-cache
33
+ X-Frame-Options:
34
+ - DENY
35
+ Set-Cookie:
36
+ - csrf_token=6448c9d779f6621c4baa59707aea63d; expires="Tue, 11-Jun-2013 04:31:05
37
+ GMT"; Max-Age=600; Path=/
38
+ - metrics=f7d9eb6e751429503ce76107b06ca1ea52cdc81556b48fbb6e8442cdaf2bd845ea18f402;
39
+ Path=/
40
+ Content-Length:
41
+ - '2'
42
+ body:
43
+ encoding: US-ASCII
44
+ string: '[]'
45
+ http_version:
46
+ recorded_at: Tue, 11 Jun 2013 04:21:09 GMT
47
+ recorded_with: VCR 2.5.0
@@ -0,0 +1,229 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: delete
5
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/tunnels/7a4815f52407435581517ffd4d71c3a7
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - '*/*; q=0.5, application/xml'
12
+ Accept-Encoding:
13
+ - gzip, deflate
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Server:
22
+ - nginx/0.7.62
23
+ Date:
24
+ - Tue, 11 Jun 2013 05:19:12 GMT
25
+ Content-Type:
26
+ - application/json
27
+ Connection:
28
+ - keep-alive
29
+ Pragma:
30
+ - no-cache
31
+ Cache-Control:
32
+ - no-cache
33
+ X-Frame-Options:
34
+ - DENY
35
+ Set-Cookie:
36
+ - csrf_token=987dda6d5a72ac5e15e804c6f81417f; expires="Tue, 11-Jun-2013 05:36:18
37
+ GMT"; Max-Age=600; Path=/
38
+ - metrics=8c3aec73d026c2739e78176dc7e48da9da88b13df2efaffa9d5442ca9d3bf56ee75f008c;
39
+ Path=/
40
+ Content-Length:
41
+ - '58'
42
+ body:
43
+ encoding: US-ASCII
44
+ string: '{"result": true, "id": "7a4815f52407435581517ffd4d71c3a7"}'
45
+ http_version:
46
+ recorded_at: Tue, 11 Jun 2013 05:26:22 GMT
47
+ - request:
48
+ method: get
49
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/tunnels/fcf7b980037b4a37aa5ff19808e46da7
50
+ body:
51
+ encoding: US-ASCII
52
+ string: ''
53
+ headers:
54
+ Accept:
55
+ - '*/*; q=0.5, application/xml'
56
+ Accept-Encoding:
57
+ - gzip, deflate
58
+ User-Agent:
59
+ - Ruby
60
+ response:
61
+ status:
62
+ code: 200
63
+ message: OK
64
+ headers:
65
+ Server:
66
+ - nginx/0.7.62
67
+ Date:
68
+ - Tue, 11 Jun 2013 06:29:23 GMT
69
+ Content-Type:
70
+ - application/json
71
+ Connection:
72
+ - keep-alive
73
+ Pragma:
74
+ - no-cache
75
+ Cache-Control:
76
+ - no-cache
77
+ X-Frame-Options:
78
+ - DENY
79
+ Set-Cookie:
80
+ - csrf_token=cb99c690ba4dec71e689ccc2368aee20; expires="Tue, 11-Jun-2013 06:46:29
81
+ GMT"; Max-Age=600; Path=/
82
+ - metrics=5f26b4576e57ec0c75993853df481a41035c26d7768a87c9e90c4212bf075d7cfe42cc17;
83
+ Path=/
84
+ Content-Length:
85
+ - '699'
86
+ body:
87
+ encoding: US-ASCII
88
+ string: '{"status": "terminated", "direct_domains": null, "shutdown_time": null,
89
+ "ssh_port": 443, "user_shutdown": true, "use_caching_proxy": true, "creation_time":
90
+ 1370924241, "domain_names": ["sauce-connect.proxy"], "shared_tunnel": false,
91
+ "tunnel_identifier": null, "host": "maki10141.miso.saucelabs.com", "owner":
92
+ "<SAUCE_USERNAME>", "use_kgp": true, "id": "fcf7b980037b4a37aa5ff19808e46da7",
93
+ "metadata": {"ScriptRelease": 35, "PythonVersion": "2.5.1", "Platform": "Java-1.7.0_13-Java_HotSpot-TM-_64-Bit_Server_VM,_23.7-b01,_Oracle_Corporation-on-Mac_OS_X-10.8.3-x86_64",
94
+ "OwnerHost": "127.0.0.1", "Build": "35", "Release": "3.0-r24", "OwnerPorts":
95
+ ["57539"], "ScriptName": "sauce_connect", "Ports": ["80"]}}'
96
+ http_version:
97
+ recorded_at: Tue, 11 Jun 2013 06:36:33 GMT
98
+ - request:
99
+ method: get
100
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/tunnels
101
+ body:
102
+ encoding: US-ASCII
103
+ string: ''
104
+ headers:
105
+ Accept:
106
+ - '*/*; q=0.5, application/xml'
107
+ Accept-Encoding:
108
+ - gzip, deflate
109
+ User-Agent:
110
+ - Ruby
111
+ response:
112
+ status:
113
+ code: 200
114
+ message: OK
115
+ headers:
116
+ Server:
117
+ - nginx/0.7.62
118
+ Date:
119
+ - Tue, 11 Jun 2013 06:49:07 GMT
120
+ Content-Type:
121
+ - application/json
122
+ Connection:
123
+ - keep-alive
124
+ Pragma:
125
+ - no-cache
126
+ Cache-Control:
127
+ - no-cache
128
+ X-Frame-Options:
129
+ - DENY
130
+ Set-Cookie:
131
+ - csrf_token=90135ac06149df70abf84010839f0d84; expires="Tue, 11-Jun-2013 07:06:13
132
+ GMT"; Max-Age=600; Path=/
133
+ - metrics=74fb9ada210b5a86c395fa69c78b7ece36c584aab137e19870e94bcdb0a2e17bbd96f144;
134
+ Path=/
135
+ Content-Length:
136
+ - '36'
137
+ body:
138
+ encoding: US-ASCII
139
+ string: &1 !ruby/string
140
+ str: '["5da3256813fd409ba7927004b499a5cf"]'
141
+ net_http_res: !ruby/object:Net::HTTPOK
142
+ http_version: '1.0'
143
+ code: '200'
144
+ message: OK
145
+ header:
146
+ server:
147
+ - nginx/0.7.62
148
+ date:
149
+ - Tue, 11 Jun 2013 06:49:07 GMT
150
+ content-type:
151
+ - application/json
152
+ connection:
153
+ - keep-alive
154
+ pragma:
155
+ - no-cache
156
+ cache-control:
157
+ - no-cache
158
+ x-frame-options:
159
+ - DENY
160
+ set-cookie:
161
+ - csrf_token=90135ac06149df70abf84010839f0d84; expires="Tue, 11-Jun-2013
162
+ 07:06:13 GMT"; Max-Age=600; Path=/
163
+ - metrics=74fb9ada210b5a86c395fa69c78b7ece36c584aab137e19870e94bcdb0a2e17bbd96f144;
164
+ Path=/
165
+ content-length:
166
+ - '36'
167
+ body: *1
168
+ read: true
169
+ __read_body_previously_called: true
170
+ args:
171
+ :method: :get
172
+ :url: https://saucelabs.com/rest/v1/dylanatsauce/tunnels
173
+ :user: dylanatsauce
174
+ :password: 8f5210dd-f0cf-477f-b720-9f721a9679a4
175
+ code: 200
176
+ http_version:
177
+ recorded_at: Tue, 11 Jun 2013 06:56:16 GMT
178
+ - request:
179
+ method: get
180
+ uri: https://<SAUCE_USERNAME>:<SAUCE_ACCESS_KEY>@saucelabs.com/rest/v1/<SAUCE_USERNAME>/tunnels/5da3256813fd409ba7927004b499a5cf
181
+ body:
182
+ encoding: US-ASCII
183
+ string: ''
184
+ headers:
185
+ Accept:
186
+ - '*/*; q=0.5, application/xml'
187
+ Accept-Encoding:
188
+ - gzip, deflate
189
+ User-Agent:
190
+ - Ruby
191
+ response:
192
+ status:
193
+ code: 200
194
+ message: OK
195
+ headers:
196
+ Server:
197
+ - nginx/0.7.62
198
+ Date:
199
+ - Tue, 11 Jun 2013 07:03:16 GMT
200
+ Content-Type:
201
+ - application/json
202
+ Connection:
203
+ - keep-alive
204
+ Pragma:
205
+ - no-cache
206
+ Cache-Control:
207
+ - no-cache
208
+ X-Frame-Options:
209
+ - DENY
210
+ Set-Cookie:
211
+ - csrf_token=501ca123c01fa4ac294d3640990b433f; expires="Tue, 11-Jun-2013 07:20:22
212
+ GMT"; Max-Age=600; Path=/
213
+ - metrics=11c55a5a4a201f53c400274f179bc79003c0dc64d853714ac40b489994e546aba7c7f16a;
214
+ Path=/
215
+ Content-Length:
216
+ - '697'
217
+ body:
218
+ encoding: US-ASCII
219
+ string: '{"status": "terminated", "direct_domains": null, "shutdown_time": null,
220
+ "ssh_port": 443, "user_shutdown": true, "use_caching_proxy": true, "creation_time":
221
+ 1370933412, "domain_names": ["sauce-connect.proxy"], "shared_tunnel": false,
222
+ "tunnel_identifier": "", "host": "maki10141.miso.saucelabs.com", "owner":
223
+ "<SAUCE_USERNAME>", "use_kgp": true, "id": "5da3256813fd409ba7927004b499a5cf",
224
+ "metadata": {"ScriptRelease": 38, "PythonVersion": "2.5.1", "Platform": "Java-1.7.0_13-Java_HotSpot-TM-_64-Bit_Server_VM,_23.7-b01,_Oracle_Corporation-on-Mac_OS_X-10.8.3-x86_64",
225
+ "OwnerHost": "127.0.0.1", "Build": "38", "Release": "3.0-r25", "OwnerPorts":
226
+ ["64703"], "ScriptName": "sauce_connect", "Ports": ["80"]}}'
227
+ http_version:
228
+ recorded_at: Tue, 11 Jun 2013 07:10:26 GMT
229
+ recorded_with: VCR 2.5.0
@@ -66,6 +66,15 @@ describe SauceWhisk::Job do
66
66
  end
67
67
  end
68
68
 
69
+ describe "#stop" do
70
+ subject {SauceWhisk::Job.new(params.merge({"id" => "3edc8fe6d52645bf931b1003da65af1f"}))}
71
+
72
+ it "Calls the correct REST API method", :vcr => {:cassette_name => "jobs"} do
73
+ subject.stop
74
+ assert_requested :put, "https://#{ENV["SAUCE_USERNAME"]}:#{ENV["SAUCE_ACCESS_KEY"]}@saucelabs.com/rest/v1/dylanatsauce/jobs/#{subject.id}/stop"
75
+ end
76
+ end
77
+
69
78
  describe "parameters" do
70
79
  it "lets you set only the parameters which are mutable" do
71
80
  [:name, :build, :passed, :tags, :custom_data, :visibility].each do |param|
@@ -75,4 +75,20 @@ describe SauceWhisk::Jobs do
75
75
  job.should be_an_instance_of SauceWhisk::Job
76
76
  end
77
77
  end
78
+
79
+ describe "##stop", :vcr => {:cassette_name => "jobs"} do
80
+ it "calls the API correctly" do
81
+ SauceWhisk::Jobs.stop "3edc8fe6d52645bf931b1003da65af1f"
82
+ assert_requested :put, "https://#{auth}@saucelabs.com/rest/v1/dylanatsauce/jobs/3edc8fe6d52645bf931b1003da65af1f/stop", :content_type => "application/json"
83
+ end
84
+
85
+ it "does something interesting when the job is already stopped" do
86
+ SauceWhisk::Jobs.stop "9591ce8519f043f8b3bea0462923c883"
87
+ end
88
+
89
+ it "does something else interesting when the job does not exist" do
90
+ SauceWhisk::Jobs.stop "job_id"
91
+ end
92
+
93
+ end
78
94
  end
@@ -39,11 +39,45 @@ describe SauceWhisk::RestRequestBuilder do
39
39
  end
40
40
  end
41
41
 
42
+ describe "#delete", :vcr => {:cassette_name => 'jobs'} do
43
+ let(:expected_url) {"#{SauceWhisk.base_url}/#{dummy_client.resource}/identifier"}
44
+
45
+ it "calls the base URL with the delete method" do
46
+ expected_params = {:method => :delete, :url => expected_url}.merge mock_auth
47
+ RestClient::Request.should_receive(:execute).with(expected_params)
48
+ dummy_client.delete "identifier"
49
+ end
50
+ end
51
+
42
52
  describe "#put", :vcr => {:cassette_name => 'jobs'} do
53
+ let(:expected_url) {"#{SauceWhisk.base_url}/#{dummy_client.resource}/something"}
43
54
  it "calls the base URL with the put method" do
44
- expected_url = "#{SauceWhisk.base_url}/#{dummy_client.resource}/something"
45
- expected_params = {:method => :put, :url => expected_url, :payload => "another_thing", :content_type => "application/json"}.merge mock_auth
46
- RestClient::Request.should_receive(:execute).with(expected_params)
55
+ RestClient::Request.should_receive(:execute).with(hash_including({:url => expected_url}))
56
+ dummy_client.put "something", "another_thing"
57
+ end
58
+
59
+ it "includes the right content_type" do
60
+ RestClient::Request.should_receive(:execute).with(hash_including({:content_type => "application/json"}))
61
+ dummy_client.put "something", "another_thing"
62
+ end
63
+
64
+ it "includes the right method" do
65
+ RestClient::Request.should_receive(:execute).with(hash_including({:method => :put}))
66
+ dummy_client.put "something", "another_thing"
67
+ end
68
+
69
+ it "includes the content length" do
70
+ RestClient::Request.should_receive(:execute).with(hash_including(:headers => {"Content-Length" => 13}))
71
+ dummy_client.put "something", "another_thing"
72
+ end
73
+
74
+ it "includes authentication details" do
75
+ RestClient::Request.should_receive(:execute).with(hash_including(mock_auth))
76
+ dummy_client.put "something", "another_thing"
77
+ end
78
+
79
+ it "sends the payload" do
80
+ RestClient::Request.should_receive(:execute).with(hash_including({:payload => "another_thing"}))
47
81
  dummy_client.put "something", "another_thing"
48
82
  end
49
83
  end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe SauceWhisk::Tunnel do
4
+ let(:params) {{
5
+ :id => "tunnel_id",
6
+ :owner => "test_user",
7
+ :status => "open",
8
+ :host => "yacko.wacko.dot",
9
+ :creation_time => Time.now
10
+ }}
11
+
12
+ describe "#new" do
13
+ it "sets all parameters passed in" do
14
+ tunnel = SauceWhisk::Tunnel.new params
15
+ tunnel.id.should eq "tunnel_id"
16
+ tunnel.owner.should eq "test_user"
17
+ tunnel.status.should eq "open"
18
+ tunnel.host.should eq "yacko.wacko.dot"
19
+ tunnel.creation_time.should eq params[:creation_time]
20
+ end
21
+ end
22
+
23
+ describe "#stop" do
24
+ it "calls the Repository class" do
25
+ tunnel = SauceWhisk::Tunnel.new params
26
+ SauceWhisk::Tunnels.should_receive(:stop).with("tunnel_id")
27
+
28
+ tunnel.stop
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe SauceWhisk::Tunnels, :vcr => {:cassette_name => "tunnels"} do
4
+ let(:auth) {"#{ENV["SAUCE_USERNAME"]}:#{ENV["SAUCE_ACCESS_KEY"]}"}
5
+
6
+ describe "##all" do
7
+ it "lists all tunnels a user has open" do
8
+ SauceWhisk::Tunnels.all
9
+ assert_requested :get, "https://#{auth}@saucelabs.com/rest/v1/dylanatsauce/tunnels"
10
+ end
11
+
12
+ it "returns nothing when no tunnels are found", :vcr => {:cassette_name => "no_tunnels", :exclusive => true} do
13
+ tunnels = SauceWhisk::Tunnels.all
14
+ tunnels.should eq []
15
+ end
16
+
17
+ context "called without the 'fetch' parameter" do
18
+ it "returns an array of strings" do
19
+ tunnels = SauceWhisk::Tunnels.all
20
+ tunnels.should be_an_instance_of Array
21
+ tunnels.each {|tunnel| tunnel.should be_an_instance_of String }
22
+ end
23
+ end
24
+
25
+ context "called with the fetch parameter" do
26
+ it "returns an array of Tunnels" do
27
+ tunnels = SauceWhisk::Tunnels.all(:fetch)
28
+ tunnels.should be_an_instance_of Array
29
+ tunnels.each {|tunnel| tunnel.should be_an_instance_of SauceWhisk::Tunnel}
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "##fetch" do
35
+ let(:job_id) {"fcf7b980037b4a37aa5ff19808e46da7"}
36
+ it "fetches a single instance of a tunnel" do
37
+ SauceWhisk::Tunnels.fetch job_id
38
+ assert_requested :get, "https://#{auth}@saucelabs.com/rest/v1/dylanatsauce/tunnels/#{job_id}"
39
+ end
40
+
41
+ it "returns instances of Tunnel" do
42
+ tunnel = SauceWhisk::Tunnels.fetch job_id
43
+ tunnel.should be_an_instance_of SauceWhisk::Tunnel
44
+ end
45
+ end
46
+
47
+ describe "##stop" do
48
+ it "calls the correct API method" do
49
+ tunnel_id = "7a4815f52407435581517ffd4d71c3a7"
50
+ SauceWhisk::Tunnels.stop tunnel_id
51
+ assert_requested :delete, "https://#{auth}@saucelabs.com/rest/v1/dylanatsauce/tunnels/#{tunnel_id}"
52
+ end
53
+ end
54
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sauce_whisk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dylan Lacey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-02 00:00:00.000000000 Z
11
+ date: 2013-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -92,22 +92,28 @@ files:
92
92
  - LICENSE.txt
93
93
  - README.md
94
94
  - Rakefile
95
+ - changelog.markdown
95
96
  - lib/sauce_whisk.rb
96
97
  - lib/sauce_whisk/assets.rb
97
98
  - lib/sauce_whisk/jobs.rb
98
99
  - lib/sauce_whisk/rest_request_builder.rb
100
+ - lib/sauce_whisk/tunnels.rb
99
101
  - lib/sauce_whisk/version.rb
100
102
  - sauce_whisk.gemspec
101
103
  - spec/fixtures/vcr_cassettes/assets.yml
102
104
  - spec/fixtures/vcr_cassettes/jobs.yml
103
105
  - spec/fixtures/vcr_cassettes/no_jobs.yml
106
+ - spec/fixtures/vcr_cassettes/no_tunnels.yml
104
107
  - spec/fixtures/vcr_cassettes/rest_request.yml
108
+ - spec/fixtures/vcr_cassettes/tunnels.yml
105
109
  - spec/lib/sauce_whisk/asset_spec.rb
106
110
  - spec/lib/sauce_whisk/assets_spec.rb
107
111
  - spec/lib/sauce_whisk/job_spec.rb
108
112
  - spec/lib/sauce_whisk/jobs_spec.rb
109
113
  - spec/lib/sauce_whisk/rest_request_builder_spec.rb
110
114
  - spec/lib/sauce_whisk/sauce_whisk_spec.rb
115
+ - spec/lib/sauce_whisk/tunnel_spec.rb
116
+ - spec/lib/sauce_whisk/tunnels_spec.rb
111
117
  - spec/spec_helper.rb
112
118
  - vendor/psych/lib/psych.bundle
113
119
  - vendor/psych/lib/psych.rb
@@ -184,11 +190,15 @@ test_files:
184
190
  - spec/fixtures/vcr_cassettes/assets.yml
185
191
  - spec/fixtures/vcr_cassettes/jobs.yml
186
192
  - spec/fixtures/vcr_cassettes/no_jobs.yml
193
+ - spec/fixtures/vcr_cassettes/no_tunnels.yml
187
194
  - spec/fixtures/vcr_cassettes/rest_request.yml
195
+ - spec/fixtures/vcr_cassettes/tunnels.yml
188
196
  - spec/lib/sauce_whisk/asset_spec.rb
189
197
  - spec/lib/sauce_whisk/assets_spec.rb
190
198
  - spec/lib/sauce_whisk/job_spec.rb
191
199
  - spec/lib/sauce_whisk/jobs_spec.rb
192
200
  - spec/lib/sauce_whisk/rest_request_builder_spec.rb
193
201
  - spec/lib/sauce_whisk/sauce_whisk_spec.rb
202
+ - spec/lib/sauce_whisk/tunnel_spec.rb
203
+ - spec/lib/sauce_whisk/tunnels_spec.rb
194
204
  - spec/spec_helper.rb