aitch 0.5.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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