soundcloud-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ # 400 Bad Request Check if your request is a valid HTTP request.
2
+ # 401 Unauthorized Check if you've send a valid client_id or access_token.
3
+ # 403 Forbidden Access to the requested resource is forbidden.
4
+ # 404 Not Found The requested resource was not found or is not accessible to the user.
5
+ # 406 Not Acceptable The resource is not available in the format you requested.
6
+ # 422 Unprocessable Entity The request is syntaxtically correct, but of an incorrect structure for this resource. Typically you are missing required properties or use incorrect data structures (e.g. strings instead of arrays).
7
+ # 500 Internal Server Error Occurs when an unexpected error on our side happend. Please contact us to resolve that issue.
8
+ # 503 Service Unavailable Occurs when no services are available to process your request. If possible provide your app users a way to manually trigger a retry.
9
+ # 504 Gateway Timeout Occurs when the request processing took to long. Be aware that your request could still be executed and manipulate data.
10
+
11
+
12
+ module Soundcloud
13
+ module Error
14
+
15
+ class Error < Faraday::Error::ClientError; end
16
+ class ClientError < Error; end
17
+ class ServerError < Error; end
18
+ class UnexpectedError < Error; end
19
+
20
+ class BadRequest < ClientError; end
21
+ class Unauthorized < ClientError; end
22
+ class PaymentRequired < ClientError; end
23
+ class Forbidden < ClientError; end
24
+ class NotFound < ClientError; end
25
+ class NotAcceptable < ClientError; end
26
+ class UnprocessableEntity < ClientError; end
27
+ class InternalServerError < ServerError; end
28
+ class ServiceUnavailable < ServerError; end
29
+ class GatewayTimeout < ServerError; end
30
+
31
+ STATUS_ERROR_MAP = Hash.new do |h, k|
32
+ case k
33
+ when 400...500 then :ClientError
34
+ when 500...600 then :ServerError
35
+ end
36
+ end.merge(400 => :BadRequest,
37
+ 401 => :Unauthorized,
38
+ 402 => :PaymentRequired,
39
+ 403 => :Forbidden,
40
+ 404 => :NotFound,
41
+ 406 => :NotAcceptable,
42
+ 422 => :UnprocessableEntity,
43
+ 500 => :InternalServerError,
44
+ 503 => :ServiceUnavailable,
45
+ 504 => :GatewayTimeout)
46
+
47
+ def self.[](status)
48
+ self.const_get(STATUS_ERROR_MAP[status])
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,24 @@
1
+ require 'faraday'
2
+ require 'soundcloud/error'
3
+
4
+ module Soundcloud
5
+ module Middleware
6
+
7
+ class RaiseError < Faraday::Response::Middleware
8
+
9
+ def on_complete(env)
10
+ case env[:status]
11
+ when 400...600
12
+ raise Soundcloud::Error[env[:status]], response_values(env)
13
+ end
14
+ end
15
+
16
+ def response_values(env)
17
+ {:status => env[:status], :headers => env[:response_headers], :body => env[:body]}
18
+ end
19
+
20
+ end
21
+
22
+
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.unshift File.expand_path("../lib", __FILE__)
3
+ require "soundcloud-client"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "soundcloud-client"
7
+ s.version = Soundcloud::VERSION
8
+ s.authors = ["rewritten"]
9
+ s.email = ["saverio@socialfolders.me", "fabien@socialfolders.me"]
10
+ s.homepage = "https://github.com/SocialFolders/soundcloud-client"
11
+ s.summary = "Soundcloud API client"
12
+ s.description = %q{Soundcloud API client, with configurable HTTP backend}
13
+
14
+ # s.rubyforge_project = "soundcloud-client"
15
+
16
+ s.add_dependency("faraday", "~> 0.8.0")
17
+ s.add_dependency("faraday_middleware")
18
+
19
+ s.add_development_dependency("rspec")
20
+ s.add_development_dependency("rake")
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.executables = []
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,92 @@
1
+ describe "API conformity" do
2
+ before do
3
+ @item = {id: 123}
4
+ @other = {id: 456}
5
+ end
6
+
7
+ subject { Soundcloud::API.new("fake", verbose: false, stubs: STUBS) }
8
+
9
+ [
10
+ [:users, "/users"],
11
+ [:user, "/users/123"],
12
+ [:user_tracks, "/users/123/tracks"],
13
+ [:user_playlists, "/users/123/playlists"],
14
+ [:user_groups, "/users/123/groups"],
15
+ [:user_web_profiles, "/users/123/web-profiles"],
16
+ [:user_followings, "/users/123/followings"],
17
+ [:follow!, "/users/me/followings/123", :put],
18
+ [:follow?, "/users/456/followings/123"],
19
+ [:unfollow!, "/users/me/followings/123", :delete],
20
+ [:user_followers, "/users/123/followers"],
21
+ [:user_comments, "/users/123/comments"],
22
+ [:user_favorites, "/users/123/favorites"],
23
+ [:favorite!, "/users/me/favorites/123", :put],
24
+ [:favorite?, "/users/456/favorites/123"],
25
+ [:unfavorite!, "/users/me/favorites/123", :delete],
26
+ [:user_connections, "/users/123/connections"],
27
+ [:user_activities, "/users/123/activities"],
28
+ [:tracks, "/tracks"],
29
+ [:track, "/tracks/123"],
30
+ [:create_track, "/tracks", :post],
31
+ [:update_track, "/tracks/123", :put],
32
+ [:delete_track, "/tracks/123", :delete],
33
+ [:track_comments, "/tracks/123/comments"],
34
+ [:track_favoriters, "/tracks/123/favoriters"],
35
+ [:track_shared_to_users, "/tracks/123/shared-to/users"],
36
+ [:track_shared_to_emails, "/tracks/123/shared-to/emails"],
37
+ [:track_secret_token, "/tracks/123/secret-token"],
38
+ [:playlists, "/playlists"],
39
+ [:playlist, "/playlists/123"],
40
+ [:create_playlist, "/playlists", :post],
41
+ [:update_playlist, "/playlists/123", :put],
42
+ [:delete_playlist, "/playlists/123", :delete],
43
+ [:playlist_shared_to_users, "/playlists/123/shared-to/users"],
44
+ [:playlist_shared_to_emails, "/playlists/123/shared-to/emails"],
45
+ [:playlist_secret_token, "/playlists/123/secret-token"],
46
+ [:groups, "/groups"],
47
+ [:group, "/groups/123"],
48
+ [:create_group, "/groups", :post],
49
+ [:update_group, "/groups/123", :put],
50
+ [:delete_group, "/groups/123", :delete],
51
+ [:group_moderators, "/groups/123/moderators"],
52
+ [:group_members, "/groups/123/members"],
53
+ [:group_contributors, "/groups/123/contributors"],
54
+ [:group_users, "/groups/123/users"],
55
+ [:group_tracks, "/groups/123/tracks"],
56
+ [:group_pending_tracks, "/groups/123/pending-tracks"],
57
+ [:group_contributions, "/groups/123/contributions"],
58
+ [:comments, "/comments"],
59
+ [:comment, "/comments/123"],
60
+ [:apps, "/apps"],
61
+ [:app, "/apps/123"],
62
+ [:app_tracks, "/apps/123/tracks"]
63
+ ].each do |meth, path, verb=nil|
64
+ verb ||= :get
65
+ describe meth do
66
+ last_param = Soundcloud::API.instance_method(meth).parameters.last
67
+ if last_param == [:opt, :opts]
68
+ it "should #{verb.upcase} #{path} (when not given options)" do
69
+ subject.should_receive(verb).with(path, {}).and_return(mock(:response, body: nil))
70
+ subject.send meth, *[@item, @other].take(subject.method(meth).arity.abs)
71
+ end
72
+ it "should #{verb.upcase} #{path}?some=option (when given options)" do
73
+ subject.should_receive(verb).with(path, {:some=>"option"}).and_return(mock(:response, body: nil))
74
+ subject.send meth, *[@item, @other].take(subject.method(meth).arity.abs), {some: "option"}
75
+ end
76
+ else
77
+ it "should #{verb.upcase} #{path}" do
78
+ subject.stub!(verb).with(path).and_return(mock(:response, body: nil))
79
+ subject.stub!(verb).with(path, instance_of(Hash)).and_return(mock(:response, body: nil))
80
+ subject.send meth, *[@item, @other].take(subject.method(meth).arity.abs)
81
+ end
82
+ end
83
+ if last_param == [:opt, :who]
84
+ default_path = path.gsub("456", "me")
85
+ it "should #{verb.upcase} #{default_path} (when the :who param is not given)" do
86
+ subject.should_receive(verb).with(default_path).and_return(mock(:response, body: nil))
87
+ subject.send meth, *[@item, @other].take(subject.method(meth).arity.abs - 1)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,113 @@
1
+ describe :VERSION do
2
+ subject { Soundcloud::VERSION }
3
+ it { should be_a String }
4
+ it { should match /^\d+\.\d+\.\d+/ }
5
+ end
6
+
7
+
8
+ describe Soundcloud::API do
9
+
10
+ it "should raise ArgumentError when initialized with no options" do
11
+ lambda do
12
+ Soundcloud::API.new
13
+ end.should raise_error ArgumentError
14
+ end
15
+
16
+ before do
17
+ @api = Soundcloud::API.new("fake", verbose: false, stubs: STUBS)
18
+ end
19
+
20
+ subject { @api }
21
+
22
+ describe "when receiving errors" do
23
+ it "should raise correct error from a haproxy error page (503)" do
24
+ lambda {
25
+ subject.user_playlists id: "haproxy_error"
26
+ }.should raise_exception Soundcloud::Error::ServiceUnavailable
27
+ end
28
+
29
+ it "should raise correct error from a server error page (503)" do
30
+ lambda {
31
+ subject.user_playlists id: "bleh"
32
+ }.should raise_exception Soundcloud::Error::ServiceUnavailable
33
+ end
34
+ end
35
+
36
+ describe "when receiving data" do
37
+ it "should wrap the response object of a single resource in a hash" do
38
+ subject.track(123).should be_a Hash
39
+ end
40
+ it "should wrap the response object of a colletion in a list of hashes" do
41
+ result = subject.tracks
42
+ result.should be_a Array
43
+ result.should_not be_empty
44
+ result.each do |m|
45
+ m.should be_a Hash
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ describe :id_of do
52
+
53
+ it "should pass through basic types" do
54
+ subject.id_of("me").should == "me"
55
+ subject.id_of(123).should == 123
56
+ end
57
+ it "extract id from hash" do
58
+ subject.id_of({id: "my_id"}).should == "my_id"
59
+ subject.id_of({"id" => "my_id"}).should == "my_id"
60
+ end
61
+ end
62
+
63
+ describe "have connection wrappers" do
64
+ before do
65
+ @connection = mock(Faraday::Connection)
66
+ subject.stub(:connection).and_return(@connection)
67
+ end
68
+
69
+ [:get, :post, :put, :delete].each do |verb|
70
+ it "should hand out #{verb.inspect} to the connection" do
71
+ @connection.should_receive(verb).and_return(mock(:response, body: "body"))
72
+ subject.send verb
73
+ end
74
+ end
75
+ end
76
+
77
+ describe :connection do
78
+ let(:args) { Array.new }
79
+ subject { Soundcloud::API.new("fake", verbose: false).connection(*args) }
80
+ it { should_not be_nil }
81
+
82
+ describe "with no args" do
83
+ let(:args) { Array.new }
84
+ it "should be the default one" do should eql @api.class.connection end
85
+ end
86
+
87
+ describe "verbose" do
88
+ let(:args) { [{verbose: true}] }
89
+ it "should not be the default one" do should_not eql @api.class.connection end
90
+ it "should contain a logger middleware" do
91
+ subject.builder.handlers.any? { |h| h == Faraday::Response::Logger }
92
+ end
93
+ end
94
+
95
+ describe "with stubs" do
96
+ let(:args) { [{stubs: mock(:stubs)}] }
97
+ it "should not be the default one" do should_not eql @api.class.connection end
98
+ it "should end with a Stubs adapter" do
99
+ subject.builder.handlers.last.should == Faraday::Adapter::Test
100
+ end
101
+ end
102
+
103
+ it "should be verbose when the class is set as such" do
104
+ Soundcloud::API.new("fake", verbose: true).connection.builder.handlers.any? { |h| h == Faraday::Response::Logger }
105
+ end
106
+
107
+ it "should be stubbed when the class is set as such" do
108
+ Soundcloud::API.new("fake", stubs: mock(Faraday::Adapter::Test::Stubs)).connection.builder.handlers.last.should == Faraday::Adapter::Test
109
+ end
110
+
111
+ end
112
+
113
+ end
@@ -0,0 +1,2 @@
1
+ require_relative '../lib/soundcloud-client'
2
+ require_relative 'stubs'
@@ -0,0 +1,620 @@
1
+ require 'faraday'
2
+
3
+ SAMPLES = {
4
+
5
+ user: <<-JS.gsub(/\s+/, " "),
6
+ {
7
+ "id": 3207,
8
+ "permalink": "jwagener",
9
+ "username": "Johannes Wagener",
10
+ "uri": "http://api.soundcloud.com/users/3207",
11
+ "permalink_url": "http://soundcloud.com/jwagener",
12
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848",
13
+ "country": "Germany",
14
+ "full_name": "Johannes Wagener",
15
+ "city": "Berlin",
16
+ "description": "<b>Hacker at SoundCloud</b>\r\n\r\nSome of my recent Hacks:\r\n\r\nsoundiverse.com \r\nbrowse recordings with the FiRe app by artwork\r\n\r\ntopbillin.com \r\nfind people to follow on SoundCloud\r\n\r\nchatter.fm \r\nget your account hooked up with a voicebox\r\n\r\nrecbutton.com \r\nrecord straight to your soundcloud account",
17
+ "discogs_name": null,
18
+ "myspace_name": null,
19
+ "website": "http://johannes.wagener.cc",
20
+ "website_title": "johannes.wagener.cc",
21
+ "online": true,
22
+ "track_count": 12,
23
+ "playlist_count": 1,
24
+ "followers_count": 417,
25
+ "followings_count": 174,
26
+ "public_favorites_count": 26
27
+ }
28
+ JS
29
+
30
+ users: <<-JS.gsub(/\s+/, " "),
31
+ [
32
+ {
33
+ "id": 1007028,
34
+ "permalink": "lansinghiphop",
35
+ "username": "LansingHipHop",
36
+ "uri": "http://api.soundcloud.com/users/1007028",
37
+ "permalink_url": "http://soundcloud.com/lansinghiphop",
38
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001229324-2l55vi-large.jpg?142a848",
39
+ "country": "United States",
40
+ "full_name": "Lansing HipHop",
41
+ "city": "Lansing",
42
+ "description": null,
43
+ "discogs_name": null,
44
+ "myspace_name": null,
45
+ "website": null,
46
+ "website_title": null,
47
+ "online": false,
48
+ "track_count": 1,
49
+ "playlist_count": 0,
50
+ "followers_count": 97,
51
+ "followings_count": 0,
52
+ "public_favorites_count": 0
53
+ },
54
+ {
55
+ "id": 1858228,
56
+ "permalink": "chicoseah",
57
+ "username": "ChicoSeah",
58
+ "uri": "http://api.soundcloud.com/users/1858228",
59
+ "permalink_url": "http://soundcloud.com/chicoseah",
60
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001837875-prjwx9-large.jpg?142a848",
61
+ "country": null,
62
+ "full_name": "HipHop",
63
+ "city": "ZNC",
64
+ "description": null,
65
+ "discogs_name": null,
66
+ "myspace_name": null,
67
+ "website": null,
68
+ "website_title": null,
69
+ "online": false,
70
+ "track_count": 1,
71
+ "playlist_count": 0,
72
+ "followers_count": 41,
73
+ "followings_count": 3,
74
+ "public_favorites_count": 0
75
+ },
76
+ {
77
+ "id": 2168214,
78
+ "permalink": "hip-hop-2",
79
+ "username": "hip hop",
80
+ "uri": "http://api.soundcloud.com/users/2168214",
81
+ "permalink_url": "http://soundcloud.com/hip-hop-2",
82
+ "avatar_url": "http://a1.sndcdn.com/images/default_avatar_large.png?142a848",
83
+ "country": "United States",
84
+ "full_name": "hiphop",
85
+ "city": "new york",
86
+ "description": null,
87
+ "discogs_name": null,
88
+ "myspace_name": null,
89
+ "website": null,
90
+ "website_title": null,
91
+ "online": false,
92
+ "track_count": 3,
93
+ "playlist_count": 0,
94
+ "followers_count": 99,
95
+ "followings_count": 1,
96
+ "public_favorites_count": 0
97
+ },
98
+ {
99
+ "id": 1562800,
100
+ "permalink": "chedee-1",
101
+ "username": "Chedee",
102
+ "uri": "http://api.soundcloud.com/users/1562800",
103
+ "permalink_url": "http://soundcloud.com/chedee-1",
104
+ "avatar_url": "http://i1.sndcdn.com/avatars-000003117449-uj69ym-large.jpg?142a848",
105
+ "country": "Switzerland",
106
+ "full_name": "HipHop",
107
+ "city": "Zurich",
108
+ "description": "Street Guerilla",
109
+ "discogs_name": "Chedee",
110
+ "myspace_name": null,
111
+ "website": null,
112
+ "website_title": null,
113
+ "online": false,
114
+ "track_count": 7,
115
+ "playlist_count": 2,
116
+ "followers_count": 105,
117
+ "followings_count": 51,
118
+ "public_favorites_count": 1
119
+ },
120
+ {
121
+ "id": 3498284,
122
+ "permalink": "hiphop-honky",
123
+ "username": "hiphop-honky",
124
+ "uri": "http://api.soundcloud.com/users/3498284",
125
+ "permalink_url": "http://soundcloud.com/hiphop-honky",
126
+ "avatar_url": "http://i1.sndcdn.com/avatars-000002860787-fdb9ga-large.jpg?142a848",
127
+ "country": "United States",
128
+ "full_name": "HipHop Honky",
129
+ "city": "Portland",
130
+ "description": "I BELIEVE.....\r\n\r\n",
131
+ "discogs_name": null,
132
+ "myspace_name": null,
133
+ "website": null,
134
+ "website_title": null,
135
+ "online": false,
136
+ "track_count": 4,
137
+ "playlist_count": 0,
138
+ "followers_count": 9,
139
+ "followings_count": 0,
140
+ "public_favorites_count": 0
141
+ }
142
+ ]
143
+ JS
144
+
145
+ track: <<-JS.gsub(/\s+/, " "),
146
+ {
147
+ "id": 13158665,
148
+ "created_at": "2011/04/06 15:37:43 +0000",
149
+ "user_id": 3699101,
150
+ "duration": 18109,
151
+ "commentable": true,
152
+ "state": "finished",
153
+ "sharing": "public",
154
+ "tag_list": "soundcloud:source=iphone-record",
155
+ "permalink": "munching-at-tiannas-house",
156
+ "description": null,
157
+ "streamable": true,
158
+ "downloadable": true,
159
+ "genre": null,
160
+ "release": null,
161
+ "purchase_url": null,
162
+ "label_id": null,
163
+ "label_name": null,
164
+ "isrc": null,
165
+ "video_url": null,
166
+ "track_type": "recording",
167
+ "key_signature": null,
168
+ "bpm": null,
169
+ "title": "Munching at Tiannas house",
170
+ "release_year": null,
171
+ "release_month": null,
172
+ "release_day": null,
173
+ "original_format": "m4a",
174
+ "original_content_size": 10211857,
175
+ "license": "all-rights-reserved",
176
+ "uri": "http://api.soundcloud.com/tracks/13158665",
177
+ "permalink_url": "http://soundcloud.com/user2835985/munching-at-tiannas-house",
178
+ "artwork_url": null,
179
+ "waveform_url": "http://w1.sndcdn.com/fxguEjG4ax6B_m.png",
180
+ "user": {
181
+ "id": 3699101,
182
+ "permalink": "user2835985",
183
+ "username": "user2835985",
184
+ "uri": "http://api.soundcloud.com/users/3699101",
185
+ "permalink_url": "http://soundcloud.com/user2835985",
186
+ "avatar_url": "http://a1.sndcdn.com/images/default_avatar_large.png?142a848"
187
+ },
188
+ "stream_url": "http://api.soundcloud.com/tracks/13158665/stream",
189
+ "download_url": "http://api.soundcloud.com/tracks/13158665/download",
190
+ "playback_count": 0,
191
+ "download_count": 0,
192
+ "favoritings_count": 0,
193
+ "comment_count": 0,
194
+ "created_with": {
195
+ "id": 124,
196
+ "name": "SoundCloud iPhone",
197
+ "uri": "http://api.soundcloud.com/apps/124",
198
+ "permalink_url": "http://soundcloud.com/apps/iphone"
199
+ },
200
+ "attachments_uri": "http://api.soundcloud.com/tracks/13158665/attachments"
201
+ }
202
+ JS
203
+
204
+ playlist: <<-JS.gsub(/\s+/, " "),
205
+ {
206
+ "id": 405726,
207
+ "created_at": "2010/11/02 09:24:50 +0000",
208
+ "user_id": 3207,
209
+ "duration": 154516,
210
+ "sharing": "public",
211
+ "tag_list": "",
212
+ "permalink": "field-recordings",
213
+ "description": "a couple of field recordings to test http://soundiverse.com.\r\n\r\nrecorded with the fire recorder: http://soundcloud.com/apps/fire",
214
+ "streamable": true,
215
+ "downloadable": true,
216
+ "genre": "",
217
+ "release": "",
218
+ "purchase_url": null,
219
+ "label_id": null,
220
+ "label_name": "",
221
+ "type": "other",
222
+ "playlist_type": "other",
223
+ "ean": "",
224
+ "title": "Field Recordings",
225
+ "release_year": null,
226
+ "release_month": null,
227
+ "release_day": null,
228
+ "license": "cc-by",
229
+ "uri": "http://api.soundcloud.com/playlists/405726",
230
+ "permalink_url": "http://soundcloud.com/jwagener/sets/field-recordings",
231
+ "artwork_url": null,
232
+ "user": {
233
+ "id": 3207,
234
+ "permalink": "jwagener",
235
+ "username": "Johannes Wagener",
236
+ "uri": "http://api.soundcloud.com/users/3207",
237
+ "permalink_url": "http://soundcloud.com/jwagener",
238
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848"
239
+ },
240
+ "tracks": [
241
+ {
242
+ "id": 6621631,
243
+ "created_at": "2010/11/02 09:08:43 +0000",
244
+ "user_id": 3207,
245
+ "duration": 27099,
246
+ "commentable": true,
247
+ "state": "finished",
248
+ "sharing": "public",
249
+ "tag_list": "Fieldrecording geo:lat=52.527544 geo:lon=13.402905",
250
+ "permalink": "coffee-machine",
251
+ "description": "",
252
+ "streamable": true,
253
+ "downloadable": true,
254
+ "genre": "",
255
+ "release": "",
256
+ "purchase_url": null,
257
+ "label_id": null,
258
+ "label_name": "",
259
+ "isrc": "",
260
+ "video_url": null,
261
+ "track_type": "",
262
+ "key_signature": "",
263
+ "bpm": null,
264
+ "title": "coffee machine",
265
+ "release_year": null,
266
+ "release_month": null,
267
+ "release_day": null,
268
+ "original_format": "wav",
269
+ "license": "cc-by",
270
+ "uri": "http://api.soundcloud.com/tracks/6621631",
271
+ "permalink_url": "http://soundcloud.com/jwagener/coffee-machine",
272
+ "artwork_url": "http://i1.sndcdn.com/artworks-000002863219-4zpxc0-large.jpg?142a848",
273
+ "waveform_url": "http://w1.sndcdn.com/Yva1Qimi7TVd_m.png",
274
+ "user": {
275
+ "id": 3207,
276
+ "permalink": "jwagener",
277
+ "username": "Johannes Wagener",
278
+ "uri": "http://api.soundcloud.com/users/3207",
279
+ "permalink_url": "http://soundcloud.com/jwagener",
280
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848"
281
+ },
282
+ "stream_url": "http://api.soundcloud.com/tracks/6621631/stream",
283
+ "download_url": "http://api.soundcloud.com/tracks/6621631/download",
284
+ "playback_count": 436,
285
+ "download_count": 40,
286
+ "favoritings_count": 9,
287
+ "comment_count": 0,
288
+ "created_with": {
289
+ "id": 64,
290
+ "name": "FiRe - Field Recorder",
291
+ "uri": "http://api.soundcloud.com/apps/64",
292
+ "permalink_url": "http://soundcloud.com/apps/fire"
293
+ },
294
+ "attachments_uri": "http://api.soundcloud.com/tracks/6621631/attachments"
295
+ },
296
+ {
297
+ "id": 6621549,
298
+ "created_at": "2010/11/02 09:00:23 +0000",
299
+ "user_id": 3207,
300
+ "duration": 65618,
301
+ "commentable": true,
302
+ "state": "finished",
303
+ "sharing": "public",
304
+ "tag_list": "Fieldrecording geo:lat=52.528181 geo:lon=13.412658",
305
+ "permalink": "tram-in-berlin",
306
+ "description": "",
307
+ "streamable": true,
308
+ "downloadable": true,
309
+ "genre": "",
310
+ "release": "",
311
+ "purchase_url": null,
312
+ "label_id": null,
313
+ "label_name": "",
314
+ "isrc": "",
315
+ "video_url": null,
316
+ "track_type": "recording",
317
+ "key_signature": "",
318
+ "bpm": null,
319
+ "title": "tram in berlin",
320
+ "release_year": null,
321
+ "release_month": null,
322
+ "release_day": null,
323
+ "original_format": "wav",
324
+ "license": "cc-by",
325
+ "uri": "http://api.soundcloud.com/tracks/6621549",
326
+ "permalink_url": "http://soundcloud.com/jwagener/tram-in-berlin",
327
+ "artwork_url": "http://i1.sndcdn.com/artworks-000002863163-6f2aqe-large.jpg?142a848",
328
+ "waveform_url": "http://w1.sndcdn.com/u04ibjx6FYdM_m.png",
329
+ "user": {
330
+ "id": 3207,
331
+ "permalink": "jwagener",
332
+ "username": "Johannes Wagener",
333
+ "uri": "http://api.soundcloud.com/users/3207",
334
+ "permalink_url": "http://soundcloud.com/jwagener",
335
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848"
336
+ },
337
+ "stream_url": "http://api.soundcloud.com/tracks/6621549/stream",
338
+ "download_url": "http://api.soundcloud.com/tracks/6621549/download",
339
+ "playback_count": 231,
340
+ "download_count": 36,
341
+ "favoritings_count": 3,
342
+ "comment_count": 1,
343
+ "created_with": {
344
+ "id": 64,
345
+ "name": "FiRe - Field Recorder",
346
+ "uri": "http://api.soundcloud.com/apps/64",
347
+ "permalink_url": "http://soundcloud.com/apps/fire"
348
+ },
349
+ "attachments_uri": "http://api.soundcloud.com/tracks/6621549/attachments"
350
+ },
351
+ {
352
+ "id": 6668072,
353
+ "created_at": "2010/11/03 19:47:11 +0000",
354
+ "user_id": 3207,
355
+ "duration": 21871,
356
+ "commentable": true,
357
+ "state": "finished",
358
+ "sharing": "public",
359
+ "tag_list": "geo:lat=52.527529 geo:lon=13.402961",
360
+ "permalink": "alex-playing-drums",
361
+ "description": "",
362
+ "streamable": true,
363
+ "downloadable": true,
364
+ "genre": null,
365
+ "release": "",
366
+ "purchase_url": null,
367
+ "label_id": null,
368
+ "label_name": "",
369
+ "isrc": "",
370
+ "video_url": null,
371
+ "track_type": "recording",
372
+ "key_signature": "",
373
+ "bpm": null,
374
+ "title": "alex playing drums",
375
+ "release_year": null,
376
+ "release_month": null,
377
+ "release_day": null,
378
+ "original_format": "wav",
379
+ "license": "cc-by",
380
+ "uri": "http://api.soundcloud.com/tracks/6668072",
381
+ "permalink_url": "http://soundcloud.com/jwagener/alex-playing-drums",
382
+ "artwork_url": "http://i1.sndcdn.com/artworks-000002888918-takbu6-large.jpg?142a848",
383
+ "waveform_url": "http://w1.sndcdn.com/MQnxWxIH94ai_m.png",
384
+ "user": {
385
+ "id": 3207,
386
+ "permalink": "jwagener",
387
+ "username": "Johannes Wagener",
388
+ "uri": "http://api.soundcloud.com/users/3207",
389
+ "permalink_url": "http://soundcloud.com/jwagener",
390
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848"
391
+ },
392
+ "stream_url": "http://api.soundcloud.com/tracks/6668072/stream",
393
+ "download_url": "http://api.soundcloud.com/tracks/6668072/download",
394
+ "playback_count": 179,
395
+ "download_count": 35,
396
+ "favoritings_count": 1,
397
+ "comment_count": 1,
398
+ "created_with": {
399
+ "id": 64,
400
+ "name": "FiRe - Field Recorder",
401
+ "uri": "http://api.soundcloud.com/apps/64",
402
+ "permalink_url": "http://soundcloud.com/apps/fire"
403
+ },
404
+ "attachments_uri": "http://api.soundcloud.com/tracks/6668072/attachments"
405
+ },
406
+ {
407
+ "id": 6698933,
408
+ "created_at": "2010/11/04 19:09:32 +0000",
409
+ "user_id": 3207,
410
+ "duration": 12726,
411
+ "commentable": true,
412
+ "state": "finished",
413
+ "sharing": "public",
414
+ "tag_list": "geo:lat=52.528450 geo:lon=13.404099",
415
+ "permalink": "typing",
416
+ "description": "",
417
+ "streamable": true,
418
+ "downloadable": true,
419
+ "genre": null,
420
+ "release": "",
421
+ "purchase_url": null,
422
+ "label_id": null,
423
+ "label_name": "",
424
+ "isrc": "",
425
+ "video_url": null,
426
+ "track_type": "recording",
427
+ "key_signature": "",
428
+ "bpm": null,
429
+ "title": "typing",
430
+ "release_year": null,
431
+ "release_month": null,
432
+ "release_day": null,
433
+ "original_format": "wav",
434
+ "license": "cc-by",
435
+ "uri": "http://api.soundcloud.com/tracks/6698933",
436
+ "permalink_url": "http://soundcloud.com/jwagener/typing",
437
+ "artwork_url": "http://i1.sndcdn.com/artworks-000002903990-le6t7d-large.jpg?142a848",
438
+ "waveform_url": "http://w1.sndcdn.com/ZSil4IqhP6Hh_m.png",
439
+ "user": {
440
+ "id": 3207,
441
+ "permalink": "jwagener",
442
+ "username": "Johannes Wagener",
443
+ "uri": "http://api.soundcloud.com/users/3207",
444
+ "permalink_url": "http://soundcloud.com/jwagener",
445
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848"
446
+ },
447
+ "stream_url": "http://api.soundcloud.com/tracks/6698933/stream",
448
+ "download_url": "http://api.soundcloud.com/tracks/6698933/download",
449
+ "playback_count": 132,
450
+ "download_count": 29,
451
+ "favoritings_count": 0,
452
+ "comment_count": 0,
453
+ "created_with": {
454
+ "id": 64,
455
+ "name": "FiRe - Field Recorder",
456
+ "uri": "http://api.soundcloud.com/apps/64",
457
+ "permalink_url": "http://soundcloud.com/apps/fire"
458
+ },
459
+ "attachments_uri": "http://api.soundcloud.com/tracks/6698933/attachments"
460
+ },
461
+ {
462
+ "id": 6770077,
463
+ "created_at": "2010/11/07 02:45:11 +0000",
464
+ "user_id": 3207,
465
+ "duration": 27202,
466
+ "commentable": true,
467
+ "state": "finished",
468
+ "sharing": "public",
469
+ "tag_list": "geo:lat=52.531203 geo:lon=13.412165",
470
+ "permalink": "bassy",
471
+ "description": "",
472
+ "streamable": true,
473
+ "downloadable": true,
474
+ "genre": "",
475
+ "release": "",
476
+ "purchase_url": null,
477
+ "label_id": null,
478
+ "label_name": "",
479
+ "isrc": "",
480
+ "video_url": null,
481
+ "track_type": "recording",
482
+ "key_signature": "",
483
+ "bpm": null,
484
+ "title": "bassy",
485
+ "release_year": null,
486
+ "release_month": null,
487
+ "release_day": null,
488
+ "original_format": "wav",
489
+ "license": "cc-by",
490
+ "uri": "http://api.soundcloud.com/tracks/6770077",
491
+ "permalink_url": "http://soundcloud.com/jwagener/bassy",
492
+ "artwork_url": "http://i1.sndcdn.com/artworks-000002938592-960ejc-large.jpg?142a848",
493
+ "waveform_url": "http://w1.sndcdn.com/bxaiyNJt3vWK_m.png",
494
+ "user": {
495
+ "id": 3207,
496
+ "permalink": "jwagener",
497
+ "username": "Johannes Wagener",
498
+ "uri": "http://api.soundcloud.com/users/3207",
499
+ "permalink_url": "http://soundcloud.com/jwagener",
500
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848"
501
+ },
502
+ "stream_url": "http://api.soundcloud.com/tracks/6770077/stream",
503
+ "download_url": "http://api.soundcloud.com/tracks/6770077/download",
504
+ "playback_count": 160,
505
+ "download_count": 20,
506
+ "favoritings_count": 0,
507
+ "comment_count": 1,
508
+ "created_with": {
509
+ "id": 64,
510
+ "name": "FiRe - Field Recorder",
511
+ "uri": "http://api.soundcloud.com/apps/64",
512
+ "permalink_url": "http://soundcloud.com/apps/fire"
513
+ },
514
+ "attachments_uri": "http://api.soundcloud.com/tracks/6770077/attachments"
515
+ }
516
+ ]
517
+ }
518
+ JS
519
+ playlist: <<-JS.gsub(/\s+/, " "),
520
+ {
521
+ "id": 405726,
522
+ "tracks": [
523
+ {
524
+ "id": 6668072,
525
+ "downloadable": true,
526
+ "title": "alex playing drums",
527
+ "original_format": "wav",
528
+ "artwork_url": "http://i1.sndcdn.com/artworks-000002888918-takbu6-large.jpg?142a848",
529
+ "download_url": "http://api.soundcloud.com/tracks/6668072/download"
530
+ },
531
+ {
532
+ "id": 6698933,
533
+ "downloadable": true,
534
+ "title": "typing",
535
+ "original_format": "wav",
536
+ "artwork_url": "http://i1.sndcdn.com/artworks-000002903990-le6t7d-large.jpg?142a848",
537
+ "download_url": "http://api.soundcloud.com/tracks/6698933/download"
538
+ },
539
+ {
540
+ "id": 6770077,
541
+ "downloadable": true,
542
+ "title": "bassy",
543
+ "original_format": "wav",
544
+ "artwork_url": "http://i1.sndcdn.com/artworks-000002938592-960ejc-large.jpg?142a848",
545
+ "download_url": "http://api.soundcloud.com/tracks/6770077/download"
546
+ }
547
+ ]
548
+ }
549
+ JS
550
+
551
+ group: <<-JS.gsub(/\s+/, " "),
552
+ {
553
+ "id": 3,
554
+ "created_at": "2009/06/18 15:46:46 +0000",
555
+ "permalink": "made-with-ableton-live",
556
+ "name": "Made with Ableton Live!",
557
+ "short_description": "tracks produced with Ableton Live! music software, no DJ mixes!",
558
+ "description": "send your tracks, no DJ mixes please!",
559
+ "uri": "http://api.soundcloud.com/groups/3",
560
+ "artwork_url": "http://i1.sndcdn.com/artworks-000000481400-f0ynk3-large.jpg?142a848",
561
+ "permalink_url": "http://soundcloud.com/groups/made-with-ableton-live",
562
+ "creator": {
563
+ "id": 1433,
564
+ "permalink": "matas",
565
+ "username": "matas",
566
+ "uri": "http://api.soundcloud.com/users/1433",
567
+ "permalink_url": "http://soundcloud.com/matas",
568
+ "avatar_url": "http://i1.sndcdn.com/avatars-000001548772-zay6dy-large.jpg?142a848"
569
+ }
570
+ }
571
+ JS
572
+
573
+ }
574
+
575
+ RESPONSE_HEADERS = { 'Content-Type' => 'application/json' }
576
+
577
+ STUBS = Faraday::Adapter::Test::Stubs.new do |stub|
578
+ stub.get('/me/playlists') { [200, RESPONSE_HEADERS.dup, "[#{SAMPLES[:playlist]}]"] }
579
+ stub.get('/me/playlists?offset=50') { [200, RESPONSE_HEADERS.dup, "[#{SAMPLES[:playlist]},#{SAMPLES[:playlist]}]"] }
580
+ stub.get('/me/tracks?filter=downloadable&offset=0') { [200, RESPONSE_HEADERS.dup, "[#{SAMPLES[:track]}]"] }
581
+ stub.get('/playlists/405726') { [200, RESPONSE_HEADERS.dup, "#{SAMPLES[:playlist]}"] }
582
+ stub.get('/me/followings?offset=0') { [200, RESPONSE_HEADERS.dup, "[#{SAMPLES[:user]}]"] }
583
+ stub.get('/users/1234/playlists?offset=0') { [200, RESPONSE_HEADERS.dup, "[#{SAMPLES[:playlist]},#{SAMPLES[:playlist]}]"] }
584
+ %w"playlist%5Btitle%5D=test%20set&playlist%5Bsharing%5D=public&
585
+ playlist%5Btitle%5D=test%20set&playlist%5Bsharing%5D=public
586
+ playlist%5Bsharing%5D=public&playlist%5Btitle%5D=test%20set&".each do |body|
587
+ stub.post("/playlists", body) do
588
+ [201, RESPONSE_HEADERS.dup, SAMPLES[:playlist].dup]
589
+ end
590
+ end
591
+ stub.delete("/playlists/405726") {[200, RESPONSE_HEADERS.dup, ""]}
592
+ stub.post "/tracks" do
593
+ [201, RESPONSE_HEADERS.dup, SAMPLES[:track].dup]
594
+ end
595
+ stub.put("/playlists/405726") {[200, RESPONSE_HEADERS.dup, ""]}
596
+ stub.get("/tracks/13158665") { [200, RESPONSE_HEADERS.dup, "#{SAMPLES[:track]}"] }
597
+ stub.get("/me/favorites?filter=downloadable&offset=0") { [200, RESPONSE_HEADERS.dup, JSON.dump(JSON.parse(SAMPLES[:playlist])["tracks"])] }
598
+ stub.get("/me/favorites?filter=downloadable&offset=50") { [200, RESPONSE_HEADERS.dup, JSON.dump(JSON.parse(SAMPLES[:playlist])["tracks"])] }
599
+ stub.put("/me/favorites/13158665") { [200, RESPONSE_HEADERS.dup, "#{SAMPLES[:track]}"] }
600
+ stub.get("/tracks") { [200, RESPONSE_HEADERS.dup, "[#{SAMPLES[:track]}]"] }
601
+ stub.get("/tracks/123") { [200, RESPONSE_HEADERS.dup, "#{SAMPLES[:track]}"] }
602
+ stub.get("/tracks/124") { [404, RESPONSE_HEADERS.dup, "{'error': 'not found', 'status': 404}"] }
603
+ stub.get("/users/bleh/playlists") { [503, {}, "{'error': 'service unavailable', 'status': 503}"] }
604
+ stub.get("/users/haproxy_error/playlists") do [503, {'Content-Type' => 'text/html'}, <<-HTML]
605
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
606
+ <html lang="en">
607
+ <head>
608
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
609
+ <title>We were unable to process your request in time - SoundCloud</title>
610
+ </head>
611
+ <body>
612
+ <div id="maintenance">
613
+ <h1> Yikes, we were unable <br /> to process your request <br /> in time. </h1>
614
+ <span> Please reload the page or try again in a moment. </span>
615
+ </div>
616
+ </body>
617
+ </html>
618
+ HTML
619
+ end
620
+ end