magnum-pi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ require "magnum-pi/gem_ext/mechanize"
@@ -0,0 +1 @@
1
+ require "magnum-pi/gem_ext/mechanize/http"
@@ -0,0 +1 @@
1
+ require "magnum-pi/gem_ext/mechanize/http/agent"
@@ -0,0 +1,60 @@
1
+ # Taken from: http://scottwb.com/blog/2013/11/09/defeating-the-infamous-mechanize-too-many-connection-resets-bug/
2
+
3
+ class Mechanize
4
+ class HTTP
5
+ class Agent
6
+ MAX_RESET_RETRIES = 10
7
+
8
+ # We need to replace the core Mechanize HTTP method:
9
+ #
10
+ # Mechanize::HTTP::Agent#fetch
11
+ #
12
+ # with a wrapper that handles the infamous "too many connection resets"
13
+ # Mechanize bug that is described here:
14
+ #
15
+ # https://github.com/sparklemotion/mechanize/issues/123
16
+ #
17
+ # The wrapper shuts down the persistent HTTP connection when it fails with
18
+ # this error, and simply tries again. In practice, this only ever needs to
19
+ # be retried once, but I am going to let it retry a few times
20
+ # (MAX_RESET_RETRIES), just in case.
21
+ #
22
+ def fetch_with_retry(
23
+ uri,
24
+ method = :get,
25
+ headers = {},
26
+ params = [],
27
+ referer = current_page,
28
+ redirects = 0
29
+ )
30
+ action = "#{method.to_s.upcase} #{uri.to_s}"
31
+ retry_count = 0
32
+
33
+ begin
34
+ fetch_without_retry(uri, method, headers, params, referer, redirects)
35
+ rescue Net::HTTP::Persistent::Error => e
36
+ # Pass on any other type of error.
37
+ raise unless e.message =~ /too many connection resets/
38
+
39
+ # Pass on the error if we've tried too many times.
40
+ if retry_count >= MAX_RESET_RETRIES
41
+ puts "**** WARN: Mechanize retried connection reset #{MAX_RESET_RETRIES} times and never succeeded: #{action}"
42
+ raise
43
+ end
44
+
45
+ # Otherwise, shutdown the persistent HTTP connection and try again.
46
+ puts "**** WARN: Mechanize retrying connection reset error: #{action}"
47
+ retry_count += 1
48
+ self.http.shutdown
49
+ retry
50
+ end
51
+ end
52
+
53
+ # Alias so #fetch actually uses our new #fetch_with_retry to wrap the
54
+ # old one aliased as #fetch_without_retry.
55
+ alias_method :fetch_without_retry, :fetch
56
+ alias_method :fetch, :fetch_with_retry
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ module MagnumPI
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ TINY = 0
5
+
6
+ VERSION = [MAJOR, MINOR, TINY].join(".")
7
+ end
data/magnum-pi.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/magnum-pi/version", __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Paul Engel"]
6
+ gem.email = ["pm_engel@icloud.com"]
7
+ gem.summary = %q{Create an easy interface to talk with APIs}
8
+ gem.description = %q{Create an easy interface to talk with APIs}
9
+ gem.homepage = "https://github.com/archan937/magnum-pi"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "magnum-pi"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = MagnumPI::VERSION
17
+
18
+ gem.add_dependency "mechanize"
19
+ gem.add_dependency "oj"
20
+ gem.add_dependency "xml-simple"
21
+
22
+ gem.add_development_dependency "rake"
23
+ gem.add_development_dependency "pry"
24
+ gem.add_development_dependency "simplecov"
25
+ gem.add_development_dependency "minitest"
26
+ gem.add_development_dependency "mocha"
27
+ end
data/script/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler"
4
+ Bundler.require :default, :development
5
+
6
+ Dir[File.expand_path("../../examples/*.rb", __FILE__)].each do |example|
7
+ require example
8
+ end
9
+
10
+ puts "Loading MagnumPI development environment (#{MagnumPI::VERSION})"
11
+ Pry.start
@@ -0,0 +1,11 @@
1
+ require_relative "test_helper/coverage"
2
+
3
+ require "minitest/autorun"
4
+ require "mocha/setup"
5
+
6
+ def path(path)
7
+ File.expand_path "../../#{path}", __FILE__
8
+ end
9
+
10
+ require "bundler"
11
+ Bundler.require :default, :development, :test
@@ -0,0 +1,11 @@
1
+ if Dir.pwd == File.expand_path("../../..", __FILE__)
2
+ require "simplecov"
3
+ SimpleCov.coverage_dir "test/coverage"
4
+ SimpleCov.start do
5
+ add_group "MagnumPI", "lib"
6
+ add_group "Test suite", "test"
7
+ add_filter do |src|
8
+ src.filename.include?("gem_ext/mechanize")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,176 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module Unit
4
+ module API
5
+ class TestConsumer < MiniTest::Test
6
+
7
+ class Consumer
8
+ include MagnumPI::API::Consumer
9
+ def to_params(url, *args)
10
+ args[0]
11
+ end
12
+ end
13
+
14
+ class IncompleteConsumer
15
+ include MagnumPI::API::Consumer
16
+ end
17
+
18
+ describe MagnumPI::API::Consumer do
19
+ before do
20
+ @consumer = Consumer.new
21
+ @consumer.stubs(:api).returns(
22
+ :uri => "http://foo.bar",
23
+ :format => :json
24
+ )
25
+ @consumer.stubs(:resources).returns(
26
+ :statistics => [:get, "stats", MagnumPI::API::Resources::Variable.new.tap{|var| var.name = :date}]
27
+ )
28
+ end
29
+ describe "#get" do
30
+ it "makes a GET request" do
31
+ response = mock
32
+ response.expects(:content).returns('{"name": "Paul Engel"}')
33
+ @consumer.expects(:request).with(:get, "http://foo.bar", {:foo => "bar"}).returns(response)
34
+ assert_equal({"name" => "Paul Engel"}, @consumer.get(:foo => "bar"))
35
+ end
36
+ end
37
+ describe "#post" do
38
+ it "makes a POST request" do
39
+ response = mock
40
+ response.expects(:content).returns('{"name": "Paul Engel"}')
41
+ @consumer.expects(:request).with(:post, "http://foo.bar", {:foo => "bar"}).returns(response)
42
+ assert_equal({"name" => "Paul Engel"}, @consumer.post(:foo => "bar"))
43
+ end
44
+ end
45
+ describe "#download" do
46
+ it "downloads using the Mechanize agent" do
47
+ response = mock
48
+ response.expects(:save_as).with("path/to/target")
49
+ @consumer.expects(:request).with(:get, "http://foo.bar", :foo => "bar").returns(response)
50
+ @consumer.download "path/to/target", :get, :foo => "bar"
51
+ end
52
+ end
53
+ describe "#resource" do
54
+ it "interpolates passed variables and makes a request" do
55
+ @consumer.expects(:send).with(:get, "stats", "2014-03-20")
56
+ @consumer.resource :statistics, :date => "2014-03-20"
57
+ end
58
+ it "interpolates passed variables and downloads a file" do
59
+ @consumer.expects(:send).with(:download, "path/to/target", :get, "stats", "2014-03-20")
60
+ @consumer.resource :statistics, {:date => "2014-03-20"}, "path/to/target"
61
+ end
62
+ end
63
+ describe "#method_missing" do
64
+ describe "corresponding resource" do
65
+ it "delegates to #resource" do
66
+ @consumer.expects(:resource).with(:statistics, :foo, :bar)
67
+ @consumer.statistics :foo, :bar
68
+ end
69
+ end
70
+ describe "no corresponding resource" do
71
+ it "raises a NoMethodError" do
72
+ assert_raises NoMethodError do
73
+ @consumer.foo
74
+ end
75
+ end
76
+ end
77
+ end
78
+ describe "#request" do
79
+ it "is delegated to the Mechanize agent" do
80
+ @consumer.send(:agent).expects(:send).with(:get, :foo, :bar)
81
+ @consumer.send(:request, :get, :foo, :bar)
82
+ end
83
+ it "raises an error when a Mechanize::ResponseCodeError occurs" do
84
+ agent = @consumer.send(:agent)
85
+ def agent.send(*args)
86
+ raise Mechanize::ResponseCodeError.new(Struct.new(:code).new)
87
+ end
88
+ assert_raises MagnumPI::API::Consumer::Error do
89
+ @consumer.get
90
+ end
91
+ end
92
+ end
93
+ describe "#agent" do
94
+ it "returns a memoized Mechanize agent" do
95
+ assert_equal Mechanize, @consumer.send(:agent).class
96
+ assert_equal @consumer.send(:agent).object_id, @consumer.send(:agent).object_id
97
+ end
98
+ it "is configured as expected" do
99
+ assert_equal OpenSSL::SSL::VERIFY_NONE, @consumer.send(:agent).verify_mode
100
+ assert_equal Mechanize::Download, @consumer.send(:agent).pluggable_parser.default
101
+ end
102
+ end
103
+ describe "#parse_args" do
104
+ it "derives the URL and params" do
105
+ @consumer.expects(:to_url).with({:foo => "bar"}).returns("http://foo.bar")
106
+ assert_equal ["http://foo.bar", {:foo => "bar"}], @consumer.send(:parse_args, {:foo => "bar"})
107
+ end
108
+ end
109
+ describe "#parse_resource_variables" do
110
+ it "returns an array containing arguments for making a request" do
111
+ resource = @consumer.resources[:statistics]
112
+ assert_equal [:get, "stats", nil], @consumer.send(:parse_resource_variables, resource, {})
113
+ assert_equal [:get, "stats", nil], @consumer.send(:parse_resource_variables, resource, {:DATE => "2014-03-20"})
114
+ assert_equal [:get, "stats", "2014-03-20"], @consumer.send(:parse_resource_variables, resource, {:date => "2014-03-20"})
115
+ assert_equal [:get, "stats", "2014-03-20"], @consumer.send(:parse_resource_variables, resource, {"date" => "2014-03-20"})
116
+ assert_equal [:get, "stats", "2014-03-20"], @consumer.send(:parse_resource_variables, resource, ["2014-03-20"])
117
+ assert_equal [:get, "stats", "2014-03-20"], @consumer.send(:parse_resource_variables, resource, "2014-03-20")
118
+ assert_raises ArgumentError do
119
+ @consumer.send :parse_resource_variables, resource, ["2014-03-20", "foo"]
120
+ end
121
+
122
+ resource = [:post, "foobar"]
123
+ assert_equal [:post, "foobar"], @consumer.send(:parse_resource_variables, resource, {})
124
+ assert_equal [:post, "foobar"], @consumer.send(:parse_resource_variables, resource, {:DATE => "2014-03-20"})
125
+ assert_equal [:post, "foobar"], @consumer.send(:parse_resource_variables, resource, {:date => "2014-03-20"})
126
+ assert_equal [:post, "foobar"], @consumer.send(:parse_resource_variables, resource, {"date" => "2014-03-20"})
127
+ assert_equal [:post, "foobar"], @consumer.send(:parse_resource_variables, resource, ["2014-03-20"])
128
+ assert_equal [:post, "foobar"], @consumer.send(:parse_resource_variables, resource, ["2014-03-20", "foo"])
129
+ end
130
+ end
131
+ describe "#to_url" do
132
+ it "returns api[:uri]" do
133
+ assert_equal "http://foo.bar", @consumer.send(:to_url)
134
+ end
135
+ end
136
+ describe "#to_params" do
137
+ it "raises a NotImplementedError" do
138
+ assert_raises NotImplementedError do
139
+ IncompleteConsumer.new.send :to_params, "url"
140
+ end
141
+ end
142
+ end
143
+ describe "#parse_content" do
144
+ it "parses the passed content" do
145
+ assert_equal(
146
+ {
147
+ "foo" => "bar"
148
+ }, @consumer.send(:parse_content,
149
+ <<-JSON
150
+ {"foo": "bar"}
151
+ JSON
152
+ )
153
+ )
154
+ @consumer.expects(:api).returns :format => "xml"
155
+ assert_equal(
156
+ {
157
+ "bar" => ["BAR"],
158
+ "baz" => [{"hello" => "world", "content" => "Baz!"}]
159
+ }, @consumer.send(:parse_content,
160
+ <<-XML
161
+ <xml>
162
+ <bar>BAR</bar>
163
+ <baz hello="world">Baz!</baz>
164
+ </xml>
165
+ XML
166
+ )
167
+ )
168
+ @consumer.expects(:api).returns :format => "unknown"
169
+ assert_equal("foobar", @consumer.send(:parse_content, "foobar"))
170
+ end
171
+ end
172
+ end
173
+
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,53 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module Unit
4
+ module API
5
+ class TestInstance < MiniTest::Test
6
+
7
+ class Foo
8
+ include MagnumPI::API::Instance
9
+ end
10
+
11
+ describe MagnumPI::API::Instance do
12
+ describe ".initialize" do
13
+ it "defines @api and @resources" do
14
+ api = mock
15
+ api.expects(:finalize).returns(api)
16
+ resources = mock
17
+ resources.expects(:to_hash).returns(resources)
18
+
19
+ Foo.expects(:api).returns api
20
+ Foo.expects(:resources).returns resources
21
+ foo = Foo.new
22
+
23
+ assert_equal api, foo.instance_variable_get(:@api)
24
+ assert_equal resources, foo.instance_variable_get(:@resources)
25
+ end
26
+ end
27
+ describe "instances" do
28
+ before do
29
+ api = mock
30
+ api.expects(:finalize).returns(api)
31
+ Foo.expects(:api).returns api
32
+ Foo.expects(:resources).returns Hash.new
33
+ end
34
+ describe "#api" do
35
+ it "returns @api" do
36
+ foo, api = Foo.new, api
37
+ foo.instance_variable_set :@api, api
38
+ assert_equal api, foo.send(:api)
39
+ end
40
+ end
41
+ describe "#resources" do
42
+ it "returns @resources" do
43
+ foo, resources = Foo.new, resources
44
+ foo.instance_variable_set :@resources, resources
45
+ assert_equal resources, foo.send(:resources)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module Unit
4
+ module API
5
+ class TestResources < MiniTest::Test
6
+
7
+ describe MagnumPI::API::Resources do
8
+ describe ".var" do
9
+ it "returns a MagnumPI::API::Resources::Variable instance" do
10
+ variable = MagnumPI::API::Resources.new.var("foo")
11
+ assert_equal MagnumPI::API::Resources::Variable, variable.class
12
+ assert_equal "foo", variable.name
13
+ end
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,81 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module Unit
4
+ module API
5
+ class TestScheme < MiniTest::Test
6
+
7
+ describe MagnumPI::API::Scheme do
8
+ before do
9
+ @scheme = MagnumPI::API::Scheme.new
10
+ end
11
+
12
+ describe "#initialize" do
13
+ it "presets `uri` and `format`" do
14
+ assert_equal({:uri => String, :format => Symbol}, @scheme.instance_eval("_types"))
15
+ end
16
+ end
17
+
18
+ describe "#class" do
19
+ it "returns MagnumPI::API::Scheme" do
20
+ assert_equal MagnumPI::API::Scheme, @scheme.class
21
+ end
22
+ end
23
+
24
+ describe "#finalize" do
25
+ it "foo" do
26
+ @scheme.foo String
27
+ @scheme.bar "bar"
28
+ object_id = @scheme.instance_eval("_values").object_id
29
+
30
+ assert_equal({:bar => "bar"}, @scheme.finalize)
31
+ assert_equal({:foo => "foo", :bar => "bar"}, @scheme.finalize(:foo => "foo"))
32
+ assert_equal object_id, @scheme.instance_eval("_values").object_id
33
+ assert_equal({:foo => "FOO", :bar => "bar"}, @scheme.finalize(:foo => "FOO"))
34
+
35
+ assert_raises ArgumentError do
36
+ @scheme.finalize :foo => :foo
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#_types" do
42
+ it "is an internal memoized hash" do
43
+ assert_equal true, @scheme.instance_eval("_types").is_a?(Hash)
44
+ assert_equal @scheme.instance_eval("_types").object_id, @scheme.instance_eval("_types").object_id
45
+ end
46
+ end
47
+
48
+ describe "types" do
49
+ describe "defining" do
50
+ it "can only be done once" do
51
+ assert_equal({:uri => String, :format => Symbol}, @scheme.instance_eval("_types"))
52
+ @scheme.format String
53
+ assert_equal({:uri => String, :format => Symbol}, @scheme.instance_eval("_types"))
54
+ @scheme.foo String
55
+ assert_equal({:uri => String, :format => Symbol, :foo => String}, @scheme.instance_eval("_types"))
56
+ @scheme.foo Symbol
57
+ assert_equal({:uri => String, :format => Symbol, :foo => String}, @scheme.instance_eval("_types"))
58
+ end
59
+ end
60
+ describe "validating" do
61
+ describe "when setting a valid value" do
62
+ it "allows value to be set" do
63
+ assert_equal({}, @scheme.instance_eval("_values"))
64
+ @scheme.format :json
65
+ assert_equal({:format => :json}, @scheme.instance_eval("_values"))
66
+ @scheme.format :xml
67
+ assert_equal({:format => :xml}, @scheme.instance_eval("_values"))
68
+ end
69
+ end
70
+ describe "when setting an invalid value" do
71
+ it "raises an error" do
72
+ assert_equal "Invalid value for 'format': \"json\" (expected Symbol)", assert_raises(ArgumentError){ @scheme.format "json" }.message
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end