Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
shaiguitar committed Mar 27, 2013
1 parent 997b867 commit 630510e
Show file tree
Hide file tree
Showing 15 changed files with 271 additions and 4 deletions.
Binary file modified APIsPresentation.shorterbulletpoints.key
Binary file not shown.
33 changes: 33 additions & 0 deletions FIXEMS
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#TODO Modifications
Conclusions
Add "there are examples" somewhere towards the beginning...;
When possible, it's great to test the server API via the client interface. (and still have separation of concerns though)

#TODO use cistern in client example in sandbox mode

Notes:

In Sandbox mode, you could have a suite of fakewebs and run it twice like other modes, against the sandbox, HOWEVER:
The scope of what fakeweb does is limited; It doesn't save the state of the server, and that's the essential difference.
Meaning, you'll probably have tests that set things up in the server, and with a sandbox, tests depending on a certain state will work
but with fakeweb you'll need to have a different type of response for each of those states, so in local development you'll still have to
use a sandbox before using fakeweb.
Also, wouldn't be simple to ship fixtures with the client with fakeweb, since it is very coupled to the implementation of the tests.

https://github.com/lanej/cistern
New undocumented project that is super cool and is great for the use-case of being stuck in sandboxing mode, still having mocks.
It's not fully integrated:
It can be fully integrated, if you run in two modes (like mentioned later with running two modes in a server) as such:
one mode, you run the client specs against the real server (localhost, rack builder/capybara.app) and in a different mode
run the client specs against the mocked server (bundled in the client, as mocks).

It's a great way to add mocked mode to your api client even when you don't have access to server dev

Even if it's not a full integration path (in later examples, running in both modes)
In many times it's the only option you have, and it's still useful to have in third party apps using it.

It's my birthday (rails conf)

Examples
Third party apps make real. # TODO
Add in Faraday: done mocked mode. other modes explain # TODO?
15 changes: 13 additions & 2 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
## Synopsis
The three main concepts are server, client and app. The server is the API provider, the client is a library used to request the API and the app is a different application entirely that uses the library to consume the API.

The three main concepts to grasp here are:

- SERVER: The API provider.
- CLIENT: A library used to request the API.
- APP: A entirely different application that uses the client to consume the API.

In the mapper examples there's an `api` concept, which separates out the api endpoints codewise, from the server itself which provides it. Both the server.rb and the server class in api.rb should compare to SERVER in other examples.

By the end of seeing the talk the mapper examples should hopefully make at the least more sense than beforehand!

## Check out

Expand All @@ -10,4 +19,6 @@ The various approaches
fakes
mapper

Each one of them has a test file which you can run as a simple unit test file. I used Sinatra for both the server and the fakes, but the idea holds given that rails is used for the server.
Each one of them has a test file which you can run as a simple unit test file. I used Sinatra for both the server and the fakes, but the ideas here hold when using a rails server.

I also added a mapper-faraday directory which shows the mapper pattern using the faraday client in place of rack-client. The idea of using faraday should hold for all approaches: faraday and rack-client are rest clients that have similar concepts and of being able to direct traffic from them to rack_builder etc.
6 changes: 6 additions & 0 deletions example/isolation/app/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source 'https://rubygems.org'
gem 'rack'
gem 'rack-client'
gem 'rack-test'
gem 'fakeweb'
gem 'pry'
17 changes: 17 additions & 0 deletions example/isolation/app/cool_musicians_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,20 @@
# @client.mock(:post_song).and_return("ok") / Fakeweb etc.
#
# We could use a fog-like backend mocking system but we don't know that your fake corresponds to the real server info.

require '../client/client'

class SocialMusicUploader
@@uploaded_songs = []

def self.muzik_label_client
@client = MyMuzikLabelAPI::Client.new
@client.domain = 'mymuziklabel.localdev.com'
@client
end

def self.upload_song_to_muzik_label(title, words)
muzik_label_client.post_song(title, words)
end

end
27 changes: 27 additions & 0 deletions example/isolation/app/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require './cool_musicians_app'
require 'fakeweb'
require 'json'
require 'rack'
require 'rack/client'
require 'test/unit'
require 'rack/test'

class SocialMusicUploaderTest < Test::Unit::TestCase

def setup
@name = "funky"
@words = "james brown is da bomb!"
@muzik_label_client = MyMuzikLabelAPI::Client.new
@muzik_label_client.domain = "mymuziklabel.localdev.com"
# we need to mock out the server here, if we end up changing our api ../server/server this test will still pass
# and our cool social app using our record labeling company won't have broken tests even though the upload is not working
FakeWeb.register_uri(:post, "http://mymuziklabel.localdev.com/song/#{@name}", :body => {:status => "ok"}.to_json)
FakeWeb.register_uri(:get, "http://mymuziklabel.localdev.com/song/#{@name}", :body => {:words => @words}.to_json )
end

def test_it_uploads_to_muzik_label_through_our_interface
SocialMusicUploader.upload_song_to_muzik_label(@name, @words)
assert_equal @muzik_label_client.get_song(@name), @words
end

end
2 changes: 1 addition & 1 deletion example/isolation/client/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class ServerDown < StandardError; end
attr_accessor :domain

def client
@client ||= Rack::Client.new(@domain)
@client = Rack::Client.new(@domain)
end

def post_song(name, words)
Expand Down
6 changes: 6 additions & 0 deletions example/mapper-faraday/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source 'https://rubygems.org'
gem 'rack'
gem 'faraday'
gem 'rack-test'
gem 'sinatra'
gem 'pry'
55 changes: 55 additions & 0 deletions example/mapper-faraday/api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require 'sinatra'
require 'rack'
require 'faraday'
require 'json'

# This is a copy over from the mapper pattern example in mapper/ using rack client. This demonstrates using faraday instead of rack-client.
# Here I demonstrate using faraday with a fake app, but obviously the same holds for using this against real app (MyMuzikLabel.app) as well.

module MyMuzikLabelAPI

class Client
# this is the change using faraday, it's a faraday compatible way of redirecting to a rack app (which we use as our fake).
def mock!(backend)
@connection = Faraday.new(:url => 'http://mymuziklabel.com') do |faraday|
faraday.adapter :rack, backend
end
end
def post_song(name, words)
JSON.parse(@connection.post("/song/#{name}", {:words => words}.to_json).body)['status']
end
def get_song(name)
JSON.parse(@connection.get("/song/#{name}").body)['words']
end
end

class Server < Sinatra::Base
post '/song/:name' do |name|
Server.mapper.post_song(name, JSON.parse(env['rack.input'].read)['words']).to_json
end
get '/song/:name' do |name|
Server.mapper.get_song(name).to_json
end
class << self
attr_accessor :mapper
end
end

class FakeMapper
Store ||= Hash.new
def self.post_song(name, words)
Store[name] = words
{:status => "ok"}
end
def self.get_song(name)
{:words => Store[name]}
end
end

def self.fake_app
app = MyMuzikLabelAPI::Server
app.mapper = MyMuzikLabelAPI::FakeMapper
app
end

end
37 changes: 37 additions & 0 deletions example/mapper-faraday/server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require './api.rb'
require 'sinatra'
require 'rack'


class MyMuzikLabel # A representation of a rails app

# models
class User; def self.has_too_many_songs?; false; end; end
# controllers
class Application < Sinatra::Base
get '/' do; 'main server application where we are going to mount the external api we created.'; end
end



# our mapper: we can access all our server logic from within here that we didn't have access to in the api
module MuzikMapper
def self.post_song(name, words)
unless User.has_too_many_songs?
{:status => "ok"} # Song.create(:words => words, :name => name)
end
end
def self.get_song(name)
{:words => 'and all that jazz'} # {:words => Song.where(:name => name).words }
end
end



# compare this to MyMuzikLabelAPI.fake_app which just uses the fake mapper instead.
def self.app
app = Rack::Cascade.new([MyMuzikLabelAPI::Server, MyMuzikLabel::Application])
MyMuzikLabelAPI::Server.mapper = MyMuzikLabel::MuzikMapper
app
end
end
20 changes: 20 additions & 0 deletions example/mapper-faraday/test_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require './api'
require 'test/unit'

# example testing the fake. still would need to test against the real.
class ClientTestFake < Test::Unit::TestCase

def setup
@name = "rock"
@words = "and all that jazz"
@client = MyMuzikLabelAPI::Client.new
@server = MyMuzikLabelAPI.fake_app
@client.mock!(@server)
end

def test_it_can_store_and_retreives_a_song
assert_equal @client.post_song("rock", "and all that jazz"), 'ok'
assert_equal @client.get_song("rock"), "and all that jazz"
end

end
1 change: 1 addition & 0 deletions example/mapper/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def mock!(backend)
connection.backend = backend
end
def connection
# this is cheap way to abstract setting rack-client#handler (ey_api_hmac#backend) so we can pass a Rack::Builder/Capyabara.app/NetHTTP to use etc.
@connection ||= EY::ApiHMAC::BaseConnection.new
end
end
Expand Down
6 changes: 6 additions & 0 deletions example/sandbox/app/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source 'https://rubygems.org'
gem 'rack'
gem 'rack-client'
gem 'rack-test'
gem 'fakeweb'
gem 'pry'
19 changes: 18 additions & 1 deletion example/sandbox/app/cool_musicians_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,21 @@
# Providers like Salesforce, Terremark etc expose sandboxes, then you could just set your client appropriately and use that:
#
# @client.domain = "https://sandbox.my-muzik-label.com"
# @client.post_song('rock', 'all that jazz')
# @client.post_song('rock', 'all that jazz')

require '../client/client'

class SocialMusicUploader
@@uploaded_songs = []

def self.muzik_label_client
@client = MyMuzikLabelAPI::Client.new
@client.domain = 'mymuziklabel.localdev.com'
@client
end

def self.upload_song_to_muzik_label(title, words)
muzik_label_client.post_song(title, words)
end

end
31 changes: 31 additions & 0 deletions example/sandbox/app/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require './cool_musicians_app'
require 'fakeweb'
require 'json'
require 'rack'
require 'rack/client'
require 'test/unit'
require 'rack/test'

class SocialMusicUploaderTest < Test::Unit::TestCase

def setup
@name = "funky"
@words = "james brown is da bomb!"
@muzik_label_client = MyMuzikLabelAPI::Client.new
@muzik_label_client.domain = "mymuziklabel.localdev.com"

# we need to mock out the server here, if we end up changing our api ../server/server this test will still pass
# and our cool social app using our record labeling company won't have broken tests even though the upload is not working
FakeWeb.register_uri(:post, "http://mymuziklabel.localdev.com/song/#{@name}", :body => {:status => "ok"}.to_json)
FakeWeb.register_uri(:get, "http://mymuziklabel.localdev.com/song/#{@name}", :body => {:words => @words}.to_json )

# maybe, we would have access to a sandbox that muzik label gave us, we could use that in development/tests, however it would have
# a bunch of drawbacks that the 'sandbox mode' introduces.
end

def test_it_uploads_to_muzik_label_through_our_interface
SocialMusicUploader.upload_song_to_muzik_label(@name, @words)
assert_equal @muzik_label_client.get_song(@name), @words
end

end

0 comments on commit 630510e

Please sign in to comment.