moxy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :gemcutter
2
+
3
+ gemspec
@@ -0,0 +1,62 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ moxy (0.0.1)
5
+ fakefs
6
+ rack-flash
7
+ sinatra
8
+ webmock
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ addressable (2.2.6)
14
+ backports (2.3.0)
15
+ crack (0.3.1)
16
+ fakefs (0.4.0)
17
+ monkey-lib (0.5.4)
18
+ backports
19
+ multi_json (1.0.3)
20
+ rack (1.3.4)
21
+ rack-flash (0.1.2)
22
+ rack
23
+ rack-protection (1.1.4)
24
+ rack
25
+ rack-test (0.6.1)
26
+ rack (>= 1.0)
27
+ rr (1.0.4)
28
+ simplecov (0.5.4)
29
+ multi_json (~> 1.0.3)
30
+ simplecov-html (~> 0.5.3)
31
+ simplecov-html (0.5.3)
32
+ sinatra (1.3.1)
33
+ rack (>= 1.3.4, ~> 1.3)
34
+ rack-protection (>= 1.1.2, ~> 1.1)
35
+ tilt (>= 1.3.3, ~> 1.3)
36
+ sinatra-advanced-routes (0.5.1)
37
+ monkey-lib (~> 0.5.0)
38
+ sinatra (~> 1.0)
39
+ sinatra-sugar (~> 0.5.0)
40
+ sinatra-reloader (0.5.0)
41
+ sinatra (~> 1.0)
42
+ sinatra-advanced-routes (~> 0.5.0)
43
+ sinatra-sugar (0.5.1)
44
+ monkey-lib (~> 0.5.0)
45
+ sinatra (~> 1.0)
46
+ tilt (1.3.3)
47
+ watchr (0.7)
48
+ webmock (1.7.6)
49
+ addressable (> 2.2.5, ~> 2.2)
50
+ crack (>= 0.1.7)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ moxy!
57
+ rack-test
58
+ rr
59
+ simplecov
60
+ simplecov-html
61
+ sinatra-reloader
62
+ watchr
@@ -0,0 +1,84 @@
1
+ # Moxy
2
+
3
+ Moxy (or `moxy`) is a programmable mock proxy. It is an HTTP proxy exposing web hooks that you can use in order to tell it what to do, and when to do it.
4
+
5
+ For example, you might want to set up that a request to `http://google.com` will return the text `boo hoo`. Thats easy:
6
+
7
+ $ curl -d "mock_text=stub_request(:get, 'http://google.com').to_return(:body=>'boo hoo')" http://localhost:9292/__setup__
8
+
9
+ However, moxy was not made just for one-offs. It was made in order to allow you to use such things in automated integration tests.
10
+
11
+ Here is an example in Ruby:
12
+
13
+ it "should handle google crashes" do
14
+ # setup responses at moxie.
15
+ HTTParty.post('http://localhost:9292/__setup__',
16
+ :body=>{
17
+ :mock_text=>'stub_request(:get, "http://google.com?q=moxie").to_return(:status=>401)'
18
+ })
19
+
20
+ r = Google.get("http://google.com?q=moxie")
21
+
22
+ r.code.must_equal 401
23
+ end
24
+
25
+ See more languages and use cases in `/examples`.
26
+
27
+
28
+ But these kind of things can already be done using `rack-test`, and other abstraction/mocking frameworks in other languages. Moxy can be an **extremely fitting** answer to your problems when:
29
+
30
+ * The juice is not worth the squeeze. You need to integration test but you don't want/need to invest effort in convoluted HTTP abstracting test code and helpers. Just set up a real response and go.
31
+
32
+ * The language / platform is not worth the squeeze. Some platforms and languages just aren't as great as Ruby. In .NET, for example, some parts of the framework don't allow for (real) testing. Using moxy clears this up pretty easily!.
33
+
34
+ * You need a proxy which is programmable. Reply with pre-programmed responses, send back files, or just pre-program to return all sorts of errors that blow in your face.
35
+
36
+
37
+
38
+
39
+ ## Getting Started
40
+
41
+ Run `gem install moxy`
42
+
43
+
44
+ ### As a system executable
45
+
46
+ $ moxy # no arguments, default to localhost, 9292
47
+ $ moxy integration-notifier.dev.com 3000
48
+
49
+ ### As a Web app
50
+
51
+ $ git clone https://github.com/jondot/moxy
52
+ $ cd moxy; rackup
53
+
54
+ A web app is great for a dedicated integration server. Moxy will run on any rack handler.
55
+
56
+
57
+
58
+ ## Using Moxy
59
+
60
+ 1. Set `http://moxy-host:port` as a system proxy, or a proxy in your HTTP library in your code.
61
+ 2. Issue any number of POSTs to your `http://moxy-host:port/__setup__` endpoint with a POST variable named `mock_text`.
62
+
63
+ Below are some examples of `mock_text` (in each, second line describes result).
64
+
65
+ stub_request :get, "http://google.com"
66
+ (Returns an empty content with 200 HTTP status code)
67
+
68
+
69
+ stub_request(:get, "http://google.com").to_return(:body => "boo hoo!")
70
+ (Returns boo hoo! as content)
71
+
72
+
73
+ stub_request(:get, "http://google.com").to_return(:body => "boo hoo!", :code => 500)
74
+ (Returns boo hoo! as content)
75
+
76
+
77
+ ## Contributing
78
+
79
+ Fork, implement, add tests, pull request, get my everlasting thanks and a respectable place here :).
80
+
81
+
82
+ ## Copyright
83
+
84
+ Copyright (c) 2011 [Dotan Nahum](http://gplus.to/dotan) [@jondot](http://twitter.com/jondot). See MIT-LICENSE for further details.
@@ -0,0 +1,16 @@
1
+ $: << '.'
2
+ $: << File.expand_path('lib', File.dirname(__FILE__))
3
+
4
+ require 'rubygems'
5
+ require 'rake'
6
+ require 'rake/testtask'
7
+ require "bundler/gem_tasks"
8
+
9
+ require 'moxy'
10
+
11
+
12
+ Rake::TestTask.new(:spec) do |spec|
13
+ spec.libs << 'lib' << 'spec'
14
+ spec.pattern = 'spec/**/*_spec.rb'
15
+ spec.verbose = true
16
+ end
@@ -0,0 +1,6 @@
1
+ require 'bundler/setup'
2
+ require 'sinatra' # not requiring rack to avoid gem conflicts.
3
+
4
+ Rack::Server.start :config => File.expand_path('../config.ru', File.dirname(__FILE__)),
5
+ :Host => ARGV[0] || "0.0.0.0",
6
+ :Port => ARGV[1] || 9292
@@ -0,0 +1,14 @@
1
+ $: << '.'
2
+ $: << File.expand_path('lib', File.dirname(__FILE__))
3
+ require 'bundler/setup'
4
+ require 'moxy'
5
+ require 'moxy/app'
6
+
7
+
8
+ map "/" do
9
+ run Moxy::WebMockHandler.new
10
+ end
11
+
12
+ map "/__setup__" do
13
+ run Moxy::App
14
+ end
@@ -0,0 +1,28 @@
1
+ require 'httparty'
2
+ require 'minitest/autorun'
3
+
4
+ class Google
5
+ include HTTParty
6
+ format :html
7
+ end
8
+
9
+ # globally set to our moxie instance.
10
+ Google.http_proxy 'localhost', 9292
11
+
12
+
13
+ describe Google do
14
+ it "should handle google crashes" do
15
+ # setup responses at moxie.
16
+ HTTParty.post('http://localhost:9292/__setup__',
17
+ :body=>{
18
+ :mock_text=>'stub_request(:get, "http://google.com?q=moxie").to_return(:status=>401)'
19
+ })
20
+
21
+ r = Google.get("http://google.com?q=moxie")
22
+
23
+ # betcha didn't expect that one from google Search!
24
+ r.code.must_equal 401
25
+ end
26
+ end
27
+
28
+
@@ -0,0 +1,18 @@
1
+ import urllib
2
+ import unittest
3
+
4
+ # Pythonistas, set up a global proxy.
5
+ # i'm doing it with:
6
+ #
7
+ # $ export http_proxy="http://localhost:9292"
8
+ #
9
+ # before test run.
10
+
11
+ class TestGoogle(unittest.TestCase):
12
+ def test_google_unauthorized(self): # passing self, heh.
13
+ params = urllib.urlencode({'mock_text': 'stub_request(:get, "http://google.com?q=moxie").to_return(:status=>401)'})
14
+ urllib.urlopen("http://google.com/__setup__",params).read()
15
+ self.assertRaises(IOError, urllib.urlopen, "http://google.com/?q=moxie") # seriously, python.
16
+
17
+ if __name__ == '__main__':
18
+ unittest.main()
@@ -0,0 +1,8 @@
1
+ require 'webmock'
2
+ require 'moxy/version'
3
+ require 'moxy/webmock_handler'
4
+ require 'moxy/app'
5
+
6
+ module Moxy
7
+
8
+ end
@@ -0,0 +1,70 @@
1
+ require 'sinatra'
2
+ require 'rack-flash'
3
+ require 'sinatra/reloader'
4
+ require 'moxy/sandbox_eval'
5
+
6
+
7
+ module Moxy
8
+ class App < Sinatra::Base
9
+ filedir = File.dirname(__FILE__)
10
+ set :public_folder, File.expand_path("../../public", filedir)
11
+ set :views, File.expand_path("../../views", filedir)
12
+ enable :sessions
13
+ use Rack::Flash
14
+
15
+ configure(:development) do
16
+ register Sinatra::Reloader
17
+ end
18
+
19
+ helpers do
20
+ include Rack::Utils
21
+ alias_method :h, :escape_html
22
+
23
+ def current_section
24
+ url_path request.path_info.sub('/','').split('/')[0].downcase
25
+ end
26
+
27
+ def current_page
28
+ url_path request.path_info.sub('/','')
29
+ end
30
+
31
+ def url_path(*path_parts)
32
+ [ path_prefix, path_parts ].join("/").squeeze('/')
33
+ end
34
+ alias_method :u, :url_path
35
+
36
+ def path_prefix
37
+ request.env['SCRIPT_NAME']
38
+ end
39
+ end
40
+
41
+ get '/reset' do
42
+ WebMock.reset!
43
+ redirect path_prefix
44
+ end
45
+
46
+ get '/current' do
47
+ @reqs = WebMock::StubRegistry.instance.request_stubs.each { |r| r.to_s } || []
48
+ erb :current
49
+ end
50
+
51
+ get '/' do
52
+ @numreqs = WebMock::StubRegistry.instance.request_stubs.size
53
+ erb :editor
54
+ end
55
+
56
+ post '/' do
57
+ flag, res = SandboxEval.new(development?).evaluate(params[:mock_text]) if params[:mock_text]
58
+
59
+ if(flag == :ok)
60
+ flash[:notice] = "Your request #{res} have been registered."
61
+ elsif
62
+ flash[:error] = "Error registering this request: #{res}"
63
+ headers 'x-moxy-errors' => "#{flag}: #{res}"
64
+ end
65
+ redirect path_prefix
66
+ end
67
+ end
68
+ end
69
+
70
+
@@ -0,0 +1,45 @@
1
+ require 'fakefs/safe'
2
+
3
+ module Moxy
4
+ class SandboxEval
5
+ def initialize(verbose=false)
6
+ @verbose = verbose
7
+ end
8
+ def setup_code
9
+ "include WebMock::API"
10
+ end
11
+
12
+ def evaluate(unsafe_code)
13
+ # run something
14
+
15
+ begin
16
+ FakeFS.activate!
17
+ cmd = <<-EOF
18
+ #{setup_code}
19
+ FakeFS::FileSystem.clear
20
+ $SAFE = 3
21
+ $stdout = StringIO.new
22
+ begin
23
+ #{unsafe_code}
24
+ end
25
+ EOF
26
+ puts "evaling:\n-------->>\n#{cmd}\n-------->>" if @verbose
27
+
28
+ stdout_id = $stdout.to_i
29
+ $stdout = StringIO.new
30
+
31
+ result = Thread.new { eval cmd, TOPLEVEL_BINDING }.value
32
+ [:ok, result]
33
+ rescue SecurityError => e
34
+ return [:illegal, e]
35
+ rescue Exception => e
36
+ return [:error, e]
37
+ ensure
38
+ $stdout = IO.new(stdout_id)
39
+ FakeFS.deactivate!
40
+ end
41
+ end
42
+
43
+
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module Moxy
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,72 @@
1
+ module Moxy
2
+ class WebMockHandler
3
+ def self.build_request_signature(req)
4
+ uri = WebMock::Util::URI.heuristic_parse(req.url)
5
+ uri.path = uri.normalized_path.gsub("[^:]//","/")
6
+
7
+ #todo: authorization, see #https://github.com/bblimke/webmock/blob/master/lib/webmock/http_lib_adapters/net_http.rb
8
+ client_headers = {}
9
+ req.env.keys.grep(/^HTTP_/).each do |k|
10
+ client_headers[k[5..-1]] = req.env[k] #remove client headers prefix 'http_'
11
+ end
12
+
13
+ request_signature = WebMock::RequestSignature.new(
14
+ req.request_method.downcase.to_sym,
15
+ uri.to_s,
16
+ :body => req.body.read,
17
+ :headers => client_headers
18
+ )
19
+ request_signature
20
+ end
21
+
22
+ def self.build_rack_response(webmock_response)
23
+ webmock_response.raise_error_if_any
24
+ [webmock_response.status[0], webmock_response.headers || {}, bodify(webmock_response.body)]
25
+ end
26
+
27
+ def self.bodify(body)
28
+ body.respond_to?(:each) ? body : [body]
29
+ end
30
+
31
+ def call(env)
32
+ req = Rack::Request.new(env)
33
+
34
+ request_signature = Moxy::WebMockHandler.build_request_signature(req)
35
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
36
+
37
+ begin
38
+ if WebMock::StubRegistry.instance.registered_request?(request_signature)
39
+ webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
40
+ # todo - originally 'handle file name' in patron. why?
41
+ # WebMock::HttpLibAdapters::PatronAdapter.handle_file_name(req, webmock_response)
42
+ res = Moxy::WebMockHandler.build_rack_response(webmock_response)
43
+ WebMock::CallbackRegistry.invoke_callbacks({:lib => :patron}, request_signature, webmock_response)
44
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
45
+ res = handle_request_without_webmock(req)
46
+ if WebMock::CallbackRegistry.any_callbacks?
47
+ webmock_response = WebMock::HttpLibAdapters::PatronAdapter.
48
+ build_webmock_response(res)
49
+ WebMock::CallbackRegistry.invoke_callbacks({:lib => :patron, :real_request => true}, request_signature, webmock_response)
50
+ end
51
+ else
52
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
53
+ end
54
+
55
+
56
+
57
+
58
+ res[1]['content-type'] ||= 'text/html' #passing rack-lint, see http://rack.rubyforge.org/doc/SPEC.html
59
+ #todo - what about content-length when it doesn't come from
60
+ #webmock? does it get calculated automatically be webmock?
61
+
62
+ return res
63
+
64
+ rescue WebMock::NetConnectNotAllowedError => ex
65
+ return [500,
66
+ {"content-type" => "text/plain",
67
+ "content-length" => ex.message.length.to_s},
68
+ Moxy::WebMockHandler.bodify(ex.message.to_s)]
69
+ end
70
+ end
71
+ end
72
+ end