aitch 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -5
  3. data/CHANGELOG.md +38 -0
  4. data/README.md +50 -14
  5. data/Rakefile +10 -0
  6. data/aitch.gemspec +4 -2
  7. data/lib/aitch.rb +7 -2
  8. data/lib/aitch/configuration.rb +1 -12
  9. data/lib/aitch/dsl.rb +1 -0
  10. data/lib/aitch/errors.rb +1 -0
  11. data/lib/aitch/ext/to_query.rb +1 -0
  12. data/lib/aitch/location.rb +3 -2
  13. data/lib/aitch/namespace.rb +1 -0
  14. data/lib/aitch/redirect.rb +8 -2
  15. data/lib/aitch/request.rb +22 -9
  16. data/lib/aitch/response.rb +4 -13
  17. data/lib/aitch/response/body.rb +1 -0
  18. data/lib/aitch/response/description.rb +52 -51
  19. data/lib/aitch/response/errors.rb +1 -0
  20. data/lib/aitch/response_parser.rb +29 -0
  21. data/lib/aitch/response_parser/default_parser.rb +18 -0
  22. data/lib/aitch/response_parser/html_parser.rb +18 -0
  23. data/lib/aitch/response_parser/json_parser.rb +18 -0
  24. data/lib/aitch/response_parser/xml_parser.rb +18 -0
  25. data/lib/aitch/uri.rb +1 -0
  26. data/lib/aitch/utils.rb +1 -0
  27. data/lib/aitch/version.rb +2 -1
  28. data/test/aitch/aitch_test.rb +27 -0
  29. data/test/aitch/configuration_test.rb +29 -0
  30. data/test/aitch/dsl_test.rb +48 -0
  31. data/test/aitch/execute_test.rb +45 -0
  32. data/test/aitch/namespace_test.rb +12 -0
  33. data/test/aitch/request/client_https_test.rb +20 -0
  34. data/test/aitch/request/follow_redirect_test.rb +64 -0
  35. data/test/aitch/request/request_class_test.rb +28 -0
  36. data/test/aitch/request/status_code_validation_test.rb +19 -0
  37. data/test/aitch/request_test.rb +138 -0
  38. data/test/aitch/response/custom_response_parser_test.rb +31 -0
  39. data/test/aitch/response/errors_test.rb +16 -0
  40. data/test/aitch/response/html_response_test.rb +18 -0
  41. data/test/aitch/response/json_response_test.rb +18 -0
  42. data/test/aitch/response/raw_response_test.rb +11 -0
  43. data/test/aitch/response/status_3xx_test.rb +56 -0
  44. data/test/aitch/response/status_4xx_test.rb +18 -0
  45. data/test/aitch/response/status_5xx_test.rb +18 -0
  46. data/test/aitch/response/xml_response_test.rb +18 -0
  47. data/test/aitch/response_parser/html_parser_test.rb +9 -0
  48. data/test/aitch/response_parser/json_parser_test.rb +9 -0
  49. data/test/aitch/response_parser/xml_parser_test.rb +17 -0
  50. data/test/aitch/response_test.rb +114 -0
  51. data/test/aitch/uri_test.rb +37 -0
  52. data/test/aitch/utils/symbolize_keys_test.rb +9 -0
  53. data/test/aitch/utils/underscore_test.rb +12 -0
  54. data/{spec → test}/fixtures/iso8859-1.xml +0 -0
  55. data/{spec/support/request_uri.rb → test/support/helpers.rb} +12 -3
  56. data/test/test_helper.rb +15 -0
  57. metadata +70 -37
  58. data/.rspec +0 -1
  59. data/lib/aitch/html_parser.rb +0 -7
  60. data/lib/aitch/xml_parser.rb +0 -7
  61. data/spec/aitch/aitch_spec.rb +0 -71
  62. data/spec/aitch/configuration_spec.rb +0 -33
  63. data/spec/aitch/dsl_spec.rb +0 -47
  64. data/spec/aitch/html_parser_spec.rb +0 -8
  65. data/spec/aitch/namespace_spec.rb +0 -11
  66. data/spec/aitch/request_spec.rb +0 -261
  67. data/spec/aitch/response_spec.rb +0 -259
  68. data/spec/aitch/uri_spec.rb +0 -36
  69. data/spec/aitch/utils_spec.rb +0 -19
  70. data/spec/aitch/xml_parser_spec.rb +0 -16
  71. data/spec/spec_helper.rb +0 -24
  72. data/spec/support/webmock.rb +0 -15
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Aitch
2
3
  class Response
3
4
  ERRORS = {
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module Aitch
3
+ module ResponseParser
4
+ PARSERS = []
5
+
6
+ def self.prepend(name, parser)
7
+ unregister(name)
8
+ PARSERS.unshift parser
9
+ end
10
+
11
+ def self.append(name, parser)
12
+ unregister(name)
13
+ PARSERS << parser
14
+ end
15
+
16
+ def self.unregister(name)
17
+ PARSERS.delete_if {|parser| parser.type == name }
18
+ end
19
+
20
+ def self.find(content_type)
21
+ PARSERS.find {|parser| parser.match?(content_type) }
22
+ end
23
+
24
+ append :json, JSONParser
25
+ append :xml, XMLParser
26
+ append :html, HTMLParser
27
+ append :default, DefaultParser
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Aitch
3
+ module ResponseParser
4
+ module DefaultParser
5
+ def self.type
6
+ :default
7
+ end
8
+
9
+ def self.match?(content_type)
10
+ true
11
+ end
12
+
13
+ def self.load(source)
14
+ source.to_s
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Aitch
3
+ module ResponseParser
4
+ module HTMLParser
5
+ def self.type
6
+ :html
7
+ end
8
+
9
+ def self.match?(content_type)
10
+ content_type =~ /html/
11
+ end
12
+
13
+ def self.load(source)
14
+ Nokogiri::HTML(source.to_s)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Aitch
3
+ module ResponseParser
4
+ module JSONParser
5
+ def self.type
6
+ :json
7
+ end
8
+
9
+ def self.match?(content_type)
10
+ content_type =~ /json/
11
+ end
12
+
13
+ def self.load(source)
14
+ ::JSON.load(source.to_s)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Aitch
3
+ module ResponseParser
4
+ module XMLParser
5
+ def self.type
6
+ :xml
7
+ end
8
+
9
+ def self.match?(content_type)
10
+ content_type =~ /xml/
11
+ end
12
+
13
+ def self.load(source)
14
+ Nokogiri::XML(source.to_s, nil, "utf-8")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Aitch
2
3
  class URI
3
4
  extend Forwardable
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Aitch
2
3
  module Utils extend self
3
4
  def underscore(string)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Aitch
2
- VERSION = "0.5.0"
3
+ VERSION = "1.0.0"
3
4
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class AitchTest < Minitest::Test
5
+ test "defines public API" do
6
+ assert Aitch.respond_to?(:get)
7
+ assert Aitch.respond_to?(:get!)
8
+ assert Aitch.respond_to?(:post)
9
+ assert Aitch.respond_to?(:post!)
10
+ assert Aitch.respond_to?(:put)
11
+ assert Aitch.respond_to?(:put!)
12
+ assert Aitch.respond_to?(:patch)
13
+ assert Aitch.respond_to?(:patch!)
14
+ assert Aitch.respond_to?(:delete)
15
+ assert Aitch.respond_to?(:delete!)
16
+ assert Aitch.respond_to?(:head)
17
+ assert Aitch.respond_to?(:head!)
18
+ assert Aitch.respond_to?(:options)
19
+ assert Aitch.respond_to?(:options!)
20
+ assert Aitch.respond_to?(:trace)
21
+ assert Aitch.respond_to?(:trace!)
22
+ assert Aitch.respond_to?(:execute)
23
+ assert Aitch.respond_to?(:execute!)
24
+ assert Aitch.respond_to?(:config)
25
+ assert Aitch.respond_to?(:configuration)
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class ConfigurationTest < Minitest::Test
5
+ test "sets default timeout" do
6
+ assert_equal 10, Aitch::Configuration.new.timeout
7
+ end
8
+
9
+ test "sets default user agent" do
10
+ user_agent = "Aitch/#{Aitch::VERSION} (http://rubygems.org/gems/aitch)"
11
+ assert_equal user_agent, Aitch::Configuration.new.user_agent
12
+ end
13
+
14
+ test "sets default maximum redirections" do
15
+ assert_equal 5, Aitch::Configuration.new.redirect_limit
16
+ end
17
+
18
+ test "sets default headers" do
19
+ assert_equal Hash.new, Aitch::Configuration.new.default_headers
20
+ end
21
+
22
+ test "configures aitch" do
23
+ Aitch.configure do |config|
24
+ config.timeout = 15
25
+ end
26
+
27
+ assert_equal 15, Aitch.configuration.timeout
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class DslTest < Minitest::Test
5
+ let(:dsl) { Aitch::DSL.new }
6
+
7
+ test "sets url" do
8
+ dsl.url "URL"
9
+ assert_equal "URL", dsl.url
10
+ end
11
+
12
+ test "sets options" do
13
+ dsl.options "OPTIONS"
14
+ assert_equal "OPTIONS", dsl.options
15
+ end
16
+
17
+ test "sets headers" do
18
+ dsl.headers "HEADERS"
19
+ assert_equal "HEADERS", dsl.headers
20
+ end
21
+
22
+ test "sets data" do
23
+ dsl.data "DATA"
24
+ assert_equal "DATA", dsl.data
25
+ end
26
+
27
+ test "sets data through params" do
28
+ dsl.params "PARAMS"
29
+ assert_equal "PARAMS", dsl.data
30
+ end
31
+
32
+ test "sets data through body" do
33
+ dsl.body "BODY"
34
+ assert_equal "BODY", dsl.data
35
+ end
36
+
37
+ test "returns hash" do
38
+ dsl.options "OPTIONS"
39
+ dsl.headers "HEADERS"
40
+ dsl.url "URL"
41
+ dsl.data "DATA"
42
+
43
+ assert_equal "DATA", dsl.to_h[:data]
44
+ assert_equal "HEADERS", dsl.to_h[:headers]
45
+ assert_equal "URL", dsl.to_h[:url]
46
+ assert_equal "OPTIONS", dsl.to_h[:options]
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class ExecuteTest < Minitest::Test
5
+ let(:request) { stub_everything }
6
+
7
+ test "delegates to Request" do
8
+ options = {}
9
+
10
+ expected = {
11
+ request_method: "get",
12
+ url: "URL",
13
+ data: "DATA",
14
+ headers: "HEADERS",
15
+ options: options.merge(Aitch.config.to_h)
16
+ }
17
+
18
+ Aitch::Request.expects(:new).with(expected).returns(request)
19
+
20
+ Aitch.get("URL", "DATA", "HEADERS", options)
21
+ end
22
+
23
+ test "performs request" do
24
+ Aitch::Request.stubs(:new).returns(request)
25
+ request.expects(:perform)
26
+
27
+ Aitch.get("URL")
28
+ end
29
+ end
30
+
31
+ class ExecuteBangTest < Minitest::Test
32
+ test "returns response when successful" do
33
+ response = stub(error?: false)
34
+ Aitch::Request.any_instance.stubs(:perform).returns(response)
35
+
36
+ assert_equal response, Aitch.get!("URL")
37
+ end
38
+
39
+ test "raises when has errors" do
40
+ response = stub(error?: true, error: "ERROR")
41
+ Aitch::Request.any_instance.stubs(:perform).returns(response)
42
+
43
+ assert_raises("ERROR") { Aitch.get!("URL") }
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class NamespaceTest < Minitest::Test
5
+ test "isolates namespace configuration" do
6
+ ns = Aitch::Namespace.new
7
+ ns.config.user_agent = "MyLib/1.0.0"
8
+
9
+ assert_equal "MyLib/1.0.0", ns.config.user_agent
10
+ assert_match %r[^Aitch], Aitch.config.user_agent
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class ClientHttpsTest < Minitest::Test
5
+ let(:request) { build_request(url: "https://example.org") }
6
+ let(:client) { request.client }
7
+
8
+ test "sets https" do
9
+ assert client.use_ssl?
10
+ end
11
+
12
+ test "sets verification mode" do
13
+ assert_equal OpenSSL::SSL::VERIFY_PEER, client.verify_mode
14
+ end
15
+
16
+ test "sets timeout" do
17
+ request.options[:timeout] = 20
18
+ assert_equal 20, client.read_timeout
19
+ end
20
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class FollowRedirectTest < Minitest::Test
5
+ setup { Aitch.configuration.follow_redirect = true }
6
+
7
+ test "follows redirect" do
8
+ Aitch.configuration.redirect_limit = 5
9
+
10
+ register_uri(:get, "http://example.org/", location: "http://example.com/", status: 301)
11
+ register_uri(:get, "http://example.com/", location: "http://www.example.com/", status: 301)
12
+ register_uri(:get, "http://www.example.com/", body: "Hello")
13
+
14
+ response = Aitch.get("http://example.org/")
15
+
16
+ refute response.redirect?
17
+ assert_equal "Hello", response.body
18
+ assert_equal ["http://example.org/", "http://example.com/"], response.redirected_from
19
+ assert_equal "http://www.example.com/", response.url
20
+ end
21
+
22
+ test "raises when doing too many redirects" do
23
+ Aitch.configuration.redirect_limit = 1
24
+
25
+ register_uri(:get, "http://example.org/", location: "http://example.com/", status: 301)
26
+ register_uri(:get, "http://example.com/", location: "https://example.com/", status: 301)
27
+
28
+ assert_raises(Aitch::TooManyRedirectsError) {
29
+ Aitch.get("http://example.org/")
30
+ }
31
+ end
32
+
33
+ test "returns only redirection urls" do
34
+ Aitch.configuration.redirect_limit = 5
35
+
36
+ register_uri(:get, "http://example.org/", location: "http://example.com/", status: 301)
37
+ register_uri(:get, "http://example.com/", status: 200)
38
+
39
+ response = Aitch.get("http://example.org/")
40
+
41
+ assert_equal "http://example.com/", response.url
42
+ assert_equal ["http://example.org/"], response.redirected_from
43
+ end
44
+
45
+ test "honors 307 status" do
46
+ Aitch.configuration.follow_redirect = true
47
+ Aitch.configuration.redirect_limit = 5
48
+
49
+ register_uri(:post, "http://example.org/", status: 307, location: "/hi")
50
+ register_uri(:post, "http://example.org/hi", status: 307, location: "/hello")
51
+ register_uri(:post, "http://example.org/hello", status: 200)
52
+
53
+ response = Aitch.post("http://example.org/", {a: 1}, {Range: "1..100"})
54
+
55
+ assert_equal "http://example.org/hello", response.url
56
+ assert_equal 200, response.code
57
+ assert_equal ["http://example.org/", "http://example.org/hi"], response.redirected_from
58
+
59
+ request = WebMock.requests.last
60
+
61
+ assert_equal "a=1", request.body
62
+ assert_equal "1..100", request.headers["Range"]
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class RequestClassTest < Minitest::Test
5
+ test "raises with invalid method" do
6
+ error = assert_raises(Aitch::InvalidHTTPMethodError) {
7
+ build_request(request_method: "invalid").request
8
+ }
9
+
10
+ assert_equal %[unexpected HTTP verb: "invalid"], error.message
11
+ end
12
+
13
+ %w[
14
+ get
15
+ post
16
+ put
17
+ patch
18
+ delete
19
+ head
20
+ options
21
+ trace
22
+ ].each do |method|
23
+ test "instantiates #{method.upcase} method" do
24
+ request = build_request(request_method: method).request
25
+ assert_equal "Net::HTTP::#{method.capitalize}", request.class.name
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class StatusCodeValidationTest < Minitest::Test
5
+ test "raises exception when status code isn't valid" do
6
+ register_uri(:get, "http://example.org/", status: 404)
7
+
8
+ error = assert_raises(Aitch::StatusCodeError) {
9
+ Aitch.get("http://example.org/", {}, {}, expect: [200])
10
+ }
11
+
12
+ assert_equal "Expected(200 OK) <=> Actual(404 Not Found)", error.message
13
+ end
14
+
15
+ test "accepts valid status code" do
16
+ register_uri(:get, "http://example.org/", status: 200)
17
+ Aitch.get("http://example.org/", {}, {}, expect: [200])
18
+ end
19
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class RequestTest < Minitest::Test
5
+ test "sets content type" do
6
+ request = build_request(content_type: 'application/json')
7
+ assert_equal 'application/json', request.content_type
8
+ end
9
+
10
+ test "raises with invalid uri" do
11
+ assert_raises(Aitch::InvalidURIError) { build_request(url: "\\").uri }
12
+ end
13
+
14
+ test "raises on timeout" do
15
+ request = build_request(request_method: "post", url: "http://example.org")
16
+ request.client.stubs(:request).raises(Net::ReadTimeout)
17
+
18
+ assert_raises(Aitch::RequestTimeoutError) { request.perform }
19
+ end
20
+
21
+ test "raises exception for invalid http method" do
22
+ request = build_request(request_method: "invalid", url: "http://example.org")
23
+
24
+ assert_raises(Aitch::InvalidHTTPMethodError) { request.perform }
25
+ end
26
+
27
+ test "sets user agent" do
28
+ requester = build_request
29
+ request = requester.request
30
+ assert_equal requester.options[:user_agent], request["User-Agent"]
31
+ end
32
+
33
+ test "requests gzip encoding" do
34
+ request = build_request.request
35
+ assert_equal "gzip,deflate", request["Accept-Encoding"]
36
+ end
37
+
38
+ test "sets path" do
39
+ request = build_request(url: "http://example.org/some/path").request
40
+ assert_equal "/some/path", request.path
41
+ end
42
+
43
+ test "sets request body from hash" do
44
+ request = build_request(request_method: "post", data: {a: 1}).request
45
+ assert_equal "a=1", request.body
46
+ end
47
+
48
+ test "sets request body from string" do
49
+ request = build_request(request_method: "post", data: "some body").request
50
+ assert_equal "some body", request.body
51
+ end
52
+
53
+ test "sets json body from object" do
54
+ request = build_request(
55
+ request_method: "post",
56
+ data: {a: 1},
57
+ content_type: "application/json",
58
+ options: {json_parser: JSON}
59
+ ).request
60
+
61
+ expected = {a: 1}.to_json
62
+ assert_equal expected, request.body
63
+ end
64
+
65
+ test "sets json body from object (default headers)" do
66
+ request = build_request(
67
+ request_method: "post",
68
+ data: {a: 1},
69
+ options: {json_parser: JSON, default_headers: {'Content-Type' => 'application/json'}}
70
+ ).request
71
+
72
+ expected = {a: 1}.to_json
73
+ assert_equal expected, request.body
74
+ end
75
+
76
+ test "sets request body from to_h protocol" do
77
+ data = stub(to_h: {a: 1})
78
+ request = build_request(request_method: "post", data: data).request
79
+ assert_equal "a=1", request.body
80
+ end
81
+
82
+ test "sets request body from to_s protocol" do
83
+ data = stub(to_s: "some body")
84
+ request = build_request(request_method: "post", data: data).request
85
+
86
+ assert_equal "some body", request.body
87
+ end
88
+
89
+ test "sets query string from hash data" do
90
+ register_uri :get, "http://example.org/?a=1&b=2", body: "hello"
91
+ requester = build_request(data: {a: 1, b: 2})
92
+
93
+ assert_equal "hello", requester.perform.body
94
+ end
95
+
96
+ test "sets default headers" do
97
+ requester = build_request
98
+ requester.options[:default_headers] = {"HEADER" => "VALUE"}
99
+ request = requester.request
100
+
101
+ assert_equal "VALUE", request["HEADER"]
102
+ end
103
+
104
+ test "sets custom headers" do
105
+ request = build_request(headers: {"HEADER" => "VALUE"}).request
106
+ assert_equal "VALUE", request["HEADER"]
107
+ end
108
+
109
+ test "executes headers with callable protocol" do
110
+ request = build_request(headers: {"HEADER" => -> { "VALUE" }}).request
111
+ assert_equal "VALUE", request["HEADER"]
112
+ end
113
+
114
+ test "sets basic auth credentials" do
115
+ request = build_request(options: {user: "USER", password: "PASS"}).request
116
+ credentials = Base64.decode64(request["Authorization"].gsub(/Basic /, ""))
117
+
118
+ assert_equal "USER:PASS", credentials
119
+ end
120
+
121
+ test "performs request when using dsl" do
122
+ register_uri(:post, /.+/)
123
+
124
+ response = Aitch.post do
125
+ url "http://example.org/some/path"
126
+ params a: 1, b: 2
127
+ headers Rendering: "0.1"
128
+ options user: "user", password: "pass"
129
+ end
130
+
131
+ assert_equal "/some/path", last_request.uri.request_uri
132
+ assert_equal :post, last_request.method
133
+ assert_equal "a=1&b=2", last_request.body
134
+ assert_equal "0.1", last_request.headers["Rendering"]
135
+ assert_equal "user", last_request.uri.user
136
+ assert_equal "pass", last_request.uri.password
137
+ end
138
+ end