Report created: 19 June 2014

Participant Information

Experience: total programming experience: 5 years: PHP/Javascript - 4 years; Ruby on Rails - 1 year.

Development System: OS X; Text editor (Emacs) and ruby console.

 

Tasks

1. Create a simple application that returns list of tracks for an account.

For all tasks use the following accounts:

SoundCloud account:
    Login: test83571
    Password: 3819_d1

Gmail Account:
    Login: test83571@gmail.com
    Password: b5ee_3cd3

If you need sample tracks use the following samples:

https://drive.google.com/file/d/0ByJFNf4ZBEb5dWJtXy05SmdzZVk/edit?usp=sharing

Sources: download

Time spent: 30 minutes

Notes:

At first I've tried to find a method to get a user by username but the only thing I've found is searching users using :q parameter like this:

client.get('/users', :q => 'test83571')

Which is not really what I want because it probably uses some fuzzy searching algorithms and therefore may return wrong user or if I have an invalid username it won't return an error. Then I've just tried using /users/{id} method but with username in place of an id and it worked. I couldn't find anywhere in the documentation that it is possible.

2. Upload the track sample1.mp3 to the account.

Sources: download

Time spent: 30 minutes

Notes:

At first I've get a little problem with authentication. I didn't set redirect_url the application I've registered, so I was getting this error:

invalid_client: The configured redirect uri of the client application is invalid.

I thought the problem was with the parameters I'm passing to the API or it doesn't allow localhost, but then I figured out that I need to set redirect url in App settings on SoundCloud. I think it's mostly my fault but I can't think of a reason to duplicate this field in app settings and on each authentication request.

I've also tried using other redirect_url than the one in app settings and I've got the error description as parametes to redirect_url location, it's easy to understand but it should be described in the documentation anyway, in my opinion.

A note about my app:
Since it's a test application I didn't implement user authentication and I save the SoundCloud authentication token to a file in the root directory instead of a database so only one SoundCloud profile can be authenticated at a time.
Also please put an mp3 file you want to be uploaded to task2/sound.mp3

 

3. Add an ability to get the track sample1.mp3 by your application.

Sources: download

Time spent: 20 minutes

Notes:

I think there is a bug either in documentation or in the ruby client.
Here is a quote from HTTP API Guide:

# get the tracks streaming URL
stream_url = client.get(track.stream_url, :allow_redirects => true)

But instead of stream url it actually returns the mp3 data itself. I guess it's usable for some cases but it won't work if you need to actually process or save the data by chunks as you receive it.  I've tried setting allow_redirects and follow_redirects to false but the result is the same.
I couldn't find a way to get a streameable object of some kind from the API.

I think the only way is to send request to track.stream_url and use the url from Location header to stream the data manually, but it's not a good solution either because it's the job of the API to provide an interface to stream the data or at least the url I can stream data from, in my opinion.
In the end I've just used client.get(track.stream_url) to get the data and saved it to file on disk.

 

4. Update a description of the track sample1.mp3.

Sources: download

Time spent: 10 minutes

Notes:

This task was pretty straightforward. There is an example in HTTP API Guide that demonstrates how to update track description and other metadata.

# update the track's metadata
client.put(track.uri, :track => {
  :description => 'This track was recorded in Berlin',
  :genre => 'Electronic',
  :artwork_data => File.new('artwork.jpg', 'rb')
})

 

5. Implement search by word "classic" and print list of the search results.

Sources: download

Time spent: 5 minutes

Notes:

I already saw the example how to search for users and tracks in the documentation so didn't even need to look it up again.

Questions

1. Name at least three of the most difficult problems you encountered during task execution and how you solved them (documentation, forums, stackoverflow, help from a friend, ...).

  1. The problem I had with streaming in task 3 is by far the biggest one. I've used client.get(track.stream_url) method which works but not as described in the documentation.
  2. The problem with oauth authentication in task, which is mostly my fault, but it could be described clearer in the documentation. I fixed it by checking things I could've done incorrectly.
  3. Getting user info by username is not documented, I've tried a way how it could be implemented and it worked.

2. Have you experienced any unexpected errors? If yes, did you find the error messages useful?

Apart from the problem in task 3, I've tried using the API not as described in the documentation and some errors I've got are not helpful at all. For example, I've tried omitting redirect_url parameter in SoundCloud initializer and it just crashed with 'undefined method `gsub' for nil:NilClass' error when I tried to get access token. It should've checked that the parameter is missing and return a helpful error instead of just crashing.

3. Did you find that some classes or methods behave non-obviously or unexpectedly?

Apart from problem in task 3, there were no major issues if you pay attention to documentation. The API looks a bit unusual but it makes sense because it's just a simple ruby wrapper for the HTTP API.

4. Name at least three ways we can improve the documentation.

  1. Sync documentation with the implementation, it looks like the part about streaming is wrong.
  2. Fix missing parts: getting user by username in task 1, describe how authentication errors are reported (task 2).
  3. There are only examples for the ruby API, it would be very helpful to add description of all methods, their parameters and return values.
  4. Add description of Exceptions that can be thrown by API methods.
  5. It would be nice to have a list of resources that are available with and without authentication. For example if I'm building an app and I don't want to force users to sign in, it would be helpful to have a list of methods that I can use.

5. Name at least three ways we can improve design of the API.

  1. Make the API higher level and object oriented. That way a lot of errors could be handled by Ruby interpreter and it would be easier to learn how it works by looking at available methods. Create classes for main data structures like user, track, playlist, etc. and allow to manipulate resources by calling instance methods,
    for example instead of

    client.put(track.uri, :track => {
      :description => 'new track description'
    })
    

    make it like this

    track.update(description: 'new track description')
    

    or even

    track.description = 'new track description'
    

    It would be much more intuitive for Ruby developers and it would be easy to learn the API by looking at the gem's source code. I understand that to implement this higher level design it would take much more time and effort than it took for the current wrapper, but it would be much more friendly.
     
  2. Improve error handling. It seems to me that right now if any of the parameters passed to some API method are wrong then Ruby API sends request to the server and all you get is an http error from the server, something like 404 Not Found. Also if I make a typo in a name of an optional parameter, the API will just ignore the parameter and it could be very hard to debug. It would be much better if the API checked errors in method arguments before sending any data to the server and returned helpful errors with a description of missing and/or invalid arguments.
     
  3. Mixing ids into arguments to client.get, client.post and similar methods seems to me like a bad idea. It is very easy to make a wrong resource path, or forget to check some value received from the outside and pass it to the API. I think even printf-style arguments would be better, for examle:

    client.get('/tracks/:id', track_id)
    

    would be better than

    client.get('/tracks/13198684')
    

    because the API could easily check if track_id is a valid id (just numbers, no slashes or other invalid symbols) and that provided route actually exist.

 

www.apiusabilitytesting.com