harpy 1.1.0 → 2.0.0.preview1
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.
- checksums.yaml +4 -4
- data/.forgejo/workflows/test.yml +33 -0
- data/.github/workflows/test.yml +20 -0
- data/README.md +5 -6
- data/harpy.gemspec +3 -1
- data/lib/harpy/client.rb +51 -13
- data/lib/harpy/entry_point.rb +3 -3
- data/lib/harpy/gem_version.rb +3 -3
- data/lib/harpy/resource.rb +5 -4
- data/spec/harpy/client_spec.rb +67 -25
- data/spec/harpy/entry_point_spec.rb +5 -5
- data/spec/harpy/resource_spec.rb +31 -32
- data/spec/spec_helper.rb +31 -2
- metadata +23 -11
- data/.travis.yml +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6bccc44b8e3516964d14dfb259eb7b9294c42449c4f9f72876b5ed1852ed3d0
|
|
4
|
+
data.tar.gz: 989d3494a0b11a5ee04b2c472c2f3ac85920bf42296c3194c1a756f7e1ddd680
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c0d99509101d673e64f08a18d7dd0ac8335f59e462e60e51638e365fc54b5a11bb5cd43e1454052af17bab69ebb7a94b6b69dbd0de27a5d8932f694a90e61c5b
|
|
7
|
+
data.tar.gz: 83f63ada339c06bd86231d8175b767bf5e56afffd80d7862093189a28e2eb4c86cdf8243b5cd88113fca49715f8f6a94bda439983c024cbef383a171a5ee48d3
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: docker
|
|
10
|
+
container:
|
|
11
|
+
image: docker.io/library/ruby:${{ matrix.image }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
include:
|
|
16
|
+
- ruby: "3.4"
|
|
17
|
+
image: "3.4"
|
|
18
|
+
- ruby: "4.0"
|
|
19
|
+
image: "4.0"
|
|
20
|
+
- ruby: "4.1"
|
|
21
|
+
image: "4.1-rc"
|
|
22
|
+
steps:
|
|
23
|
+
- name: Install Node.js 24 for the checkout action
|
|
24
|
+
run: |
|
|
25
|
+
apt-get update
|
|
26
|
+
apt-get install -y ca-certificates curl
|
|
27
|
+
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
|
|
28
|
+
apt-get install -y nodejs
|
|
29
|
+
node --version
|
|
30
|
+
- name: Check out the repository
|
|
31
|
+
uses: actions/checkout@v7
|
|
32
|
+
- run: bundle install
|
|
33
|
+
- run: bundle exec rake
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
ruby: ["3.4", "4.0", "4.1"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
- run: bundle exec rake
|
data/README.md
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
Harpy
|
|
2
2
|
======
|
|
3
3
|
|
|
4
|
-
[](https://codeclimate.com/github/TalentBox/harpy)
|
|
4
|
+
[](https://github.com/TalentBox/harpy/actions/workflows/test.yml)
|
|
6
5
|
|
|
7
6
|
Client for REST API with HATEOAS
|
|
8
7
|
|
|
9
8
|
Dependencies
|
|
10
9
|
------------
|
|
11
10
|
|
|
12
|
-
* Ruby >=
|
|
13
|
-
* gem "
|
|
11
|
+
* Ruby >= 3.4.
|
|
12
|
+
* gem "httpx", ">= 1.0"
|
|
14
13
|
* gem "activesupport", ">= 5.2.0"
|
|
15
14
|
* gem "activemodel", ">= 5.2.0"
|
|
16
15
|
* gem "hash-deep-merge", ">= 0.1.1"
|
|
@@ -44,7 +43,7 @@ Usage
|
|
|
44
43
|
# => instance of MyModel with attributes filled in on 200
|
|
45
44
|
# => nil on 404
|
|
46
45
|
# => raises Harpy::ClientTimeout on timeout
|
|
47
|
-
# => raises Harpy::ClientError on
|
|
46
|
+
# => raises Harpy::ClientError on HTTP client error
|
|
48
47
|
# => raises Harpy::InvalidResponseCode on other response codes
|
|
49
48
|
|
|
50
49
|
# Fetch multiple by url in parallel
|
|
@@ -60,7 +59,7 @@ Usage
|
|
|
60
59
|
}
|
|
61
60
|
# => return an array of MyModel instances on 200
|
|
62
61
|
# => raises Harpy::ClientTimeout on timeout
|
|
63
|
-
# => raises Harpy::ClientError on
|
|
62
|
+
# => raises Harpy::ClientError on HTTP client error
|
|
64
63
|
# => raises Harpy::InvalidResponseCode on other response codes
|
|
65
64
|
|
|
66
65
|
# Search by first_name
|
data/harpy.gemspec
CHANGED
|
@@ -12,17 +12,19 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
s.summary = %q{Client for REST API}
|
|
13
13
|
s.description = %q{Client for REST API with HATEOAS}
|
|
14
14
|
s.license = "MIT"
|
|
15
|
+
s.required_ruby_version = ">= 3.4"
|
|
15
16
|
|
|
16
17
|
s.files = `git ls-files`.split("\n")
|
|
17
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
20
|
s.require_paths = ["lib"]
|
|
20
21
|
|
|
21
|
-
s.add_runtime_dependency "
|
|
22
|
+
s.add_runtime_dependency "httpx", ">= 1.0"
|
|
22
23
|
s.add_runtime_dependency "activesupport", [">= 5.2.0"]
|
|
23
24
|
s.add_runtime_dependency "activemodel", [">= 5.2.0"]
|
|
24
25
|
s.add_runtime_dependency "hash-deep-merge", [">= 0.1.1"]
|
|
25
26
|
|
|
26
27
|
s.add_development_dependency "rake", [">= 0.8.7"]
|
|
27
28
|
s.add_development_dependency "rspec"
|
|
29
|
+
s.add_development_dependency "webmock"
|
|
28
30
|
end
|
data/lib/harpy/client.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "base64"
|
|
2
|
+
require "openssl"
|
|
3
|
+
require "httpx"
|
|
2
4
|
require "hash_deep_merge"
|
|
3
5
|
|
|
4
6
|
module Harpy
|
|
@@ -7,6 +9,7 @@ module Harpy
|
|
|
7
9
|
|
|
8
10
|
def initialize(opts=nil)
|
|
9
11
|
self.options = (opts || {})
|
|
12
|
+
@session = HTTPX::Session.new
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
def get(url_or_urls, opts=nil)
|
|
@@ -34,33 +37,68 @@ module Harpy
|
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def run(requests)
|
|
37
|
-
requests.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
return [] if requests.empty?
|
|
41
|
+
|
|
42
|
+
responses = @session.request(*requests)
|
|
43
|
+
responses.is_a?(Array) ? responses : [responses]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def response_status(response)
|
|
47
|
+
response.is_a?(HTTPX::ErrorResponse) ? 0 : response.status
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
def invalid_code(response)
|
|
43
|
-
if response.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
if response.is_a?(HTTPX::ErrorResponse)
|
|
52
|
+
if response.error.is_a?(HTTPX::TimeoutError)
|
|
53
|
+
raise ClientTimeout
|
|
54
|
+
else
|
|
55
|
+
raise ClientError, response.error.message
|
|
56
|
+
end
|
|
47
57
|
else
|
|
48
|
-
raise InvalidResponseCode, response.
|
|
58
|
+
raise InvalidResponseCode, response.status.to_s
|
|
49
59
|
end
|
|
50
60
|
end
|
|
51
61
|
|
|
52
62
|
private
|
|
53
63
|
|
|
54
64
|
def request(method, urls, opts=nil)
|
|
55
|
-
opts = options.deep_merge(opts || {})
|
|
65
|
+
opts = httpx_options(options.deep_merge(opts || {}))
|
|
56
66
|
case urls
|
|
57
67
|
when Array
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
urls.collect do |url|
|
|
69
|
+
@session.build_request(method, url, opts.dup)
|
|
60
70
|
end
|
|
61
71
|
else
|
|
62
|
-
|
|
72
|
+
@session.request(method.to_s.upcase, urls, **opts)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def httpx_options(opts)
|
|
77
|
+
timeout = opts[:timeout]
|
|
78
|
+
if timeout.is_a?(Numeric)
|
|
79
|
+
opts[:timeout] = {total_request_timeout: timeout.to_f / 1000} if timeout.positive?
|
|
80
|
+
opts.delete(:timeout) unless timeout.positive?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
ssl_options = (opts[:ssl] || {}).dup
|
|
84
|
+
if opts.key?(:ssl_verifypeer)
|
|
85
|
+
verify_peer = opts.delete(:ssl_verifypeer)
|
|
86
|
+
ssl_options[:verify_mode] = verify_peer ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
87
|
+
end
|
|
88
|
+
if opts.key?(:ssl_verifyhost)
|
|
89
|
+
verify_host = opts.delete(:ssl_verifyhost)
|
|
90
|
+
ssl_options[:verify_hostname] = verify_host != false && verify_host != 0
|
|
91
|
+
end
|
|
92
|
+
opts[:ssl] = ssl_options unless ssl_options.empty?
|
|
93
|
+
|
|
94
|
+
username = opts.delete(:username)
|
|
95
|
+
password = opts.delete(:password)
|
|
96
|
+
if username || password
|
|
97
|
+
headers = (opts[:headers] || {}).dup
|
|
98
|
+
headers["Authorization"] ||= "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
|
99
|
+
opts[:headers] = headers
|
|
63
100
|
end
|
|
101
|
+
opts
|
|
64
102
|
end
|
|
65
103
|
end
|
|
66
104
|
end
|
data/lib/harpy/entry_point.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Harpy
|
|
|
10
10
|
|
|
11
11
|
def resource_url(resource_type)
|
|
12
12
|
response = Harpy.client.get url
|
|
13
|
-
case response
|
|
13
|
+
case Harpy.client.response_status(response)
|
|
14
14
|
when 200
|
|
15
15
|
body = JSON.parse response.body
|
|
16
16
|
link = (body["link"] || []).detect{|link| link["rel"] == resource_type}
|
|
@@ -22,9 +22,9 @@ module Harpy
|
|
|
22
22
|
|
|
23
23
|
def urn(urn)
|
|
24
24
|
response = Harpy.client.get "#{url}/#{urn}"
|
|
25
|
-
case response
|
|
25
|
+
case Harpy.client.response_status(response)
|
|
26
26
|
when 301
|
|
27
|
-
response.
|
|
27
|
+
response.headers["location"]
|
|
28
28
|
when 404
|
|
29
29
|
nil
|
|
30
30
|
else
|
data/lib/harpy/gem_version.rb
CHANGED
data/lib/harpy/resource.rb
CHANGED
|
@@ -62,7 +62,7 @@ module Harpy
|
|
|
62
62
|
|
|
63
63
|
def search(conditions={})
|
|
64
64
|
response = Harpy.client.get url, :params => conditions
|
|
65
|
-
case response
|
|
65
|
+
case Harpy.client.response_status(response)
|
|
66
66
|
when 200
|
|
67
67
|
parsed = JSON.parse response.body
|
|
68
68
|
items = parsed[resource_name].collect{|model| new model}
|
|
@@ -109,7 +109,7 @@ module Harpy
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def from_url_handler(response)
|
|
112
|
-
case response
|
|
112
|
+
case Harpy.client.response_status(response)
|
|
113
113
|
when 200
|
|
114
114
|
new JSON.parse response.body
|
|
115
115
|
when 404
|
|
@@ -120,7 +120,7 @@ module Harpy
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
def delete_from_url_handler(response)
|
|
123
|
-
case response
|
|
123
|
+
case Harpy.client.response_status(response)
|
|
124
124
|
when 204
|
|
125
125
|
true
|
|
126
126
|
when 404
|
|
@@ -228,7 +228,7 @@ module Harpy
|
|
|
228
228
|
end
|
|
229
229
|
|
|
230
230
|
def process_response(response, context)
|
|
231
|
-
case response
|
|
231
|
+
case Harpy.client.response_status(response)
|
|
232
232
|
when 200, 201, 302
|
|
233
233
|
@attrs.merge! JSON.parse(response.body)
|
|
234
234
|
true
|
|
@@ -249,6 +249,7 @@ module Harpy
|
|
|
249
249
|
def read_attribute_for_validation(key)
|
|
250
250
|
@attrs[key]
|
|
251
251
|
end
|
|
252
|
+
public :read_attribute_for_validation
|
|
252
253
|
|
|
253
254
|
def method_missing(method, *args)
|
|
254
255
|
key = method.to_s
|
data/spec/harpy/client_spec.rb
CHANGED
|
@@ -25,78 +25,120 @@ describe Harpy::Client do
|
|
|
25
25
|
describe "##{method}(url, opts={})" do
|
|
26
26
|
context "with one url" do
|
|
27
27
|
before do
|
|
28
|
-
|
|
29
|
-
Typhoeus.stub(entry_url, method: method){@expected}
|
|
28
|
+
stub_request(method, entry_url).to_return(status: 200)
|
|
30
29
|
end
|
|
30
|
+
|
|
31
31
|
it "sends a #{method.to_s.upcase} to the url" do
|
|
32
|
-
|
|
32
|
+
response = subject.send(method, entry_url)
|
|
33
|
+
expect(response.status).to eq(200)
|
|
34
|
+
expect(WebMock).to have_requested(method, entry_url)
|
|
33
35
|
end
|
|
36
|
+
|
|
34
37
|
it "merges options" do
|
|
35
38
|
client = Harpy::Client.new :headers => {"Authorization" => "spec"}
|
|
36
|
-
|
|
39
|
+
stub_request(method, entry_url).to_return(status: 200)
|
|
37
40
|
response = client.send method, entry_url, :headers => {"X-Files" => "Harpy"}
|
|
38
|
-
expect(
|
|
41
|
+
expect(WebMock).to have_requested(method, entry_url).with(headers: {
|
|
42
|
+
"X-Files" => "Harpy",
|
|
43
|
+
"Authorization" => "spec"
|
|
44
|
+
})
|
|
39
45
|
end
|
|
40
46
|
end
|
|
47
|
+
|
|
41
48
|
context "with multiple urls" do
|
|
42
49
|
it "does not execute requests" do
|
|
43
50
|
expect {
|
|
44
51
|
subject.send method, [entry_url, users_url]
|
|
45
52
|
}.not_to raise_error
|
|
46
53
|
end
|
|
47
|
-
|
|
54
|
+
|
|
55
|
+
it "returns one request per url" do
|
|
48
56
|
requests = subject.send method, [entry_url, users_url]
|
|
49
57
|
expect(requests.size).to eq(2)
|
|
50
|
-
expect(requests.collect{|
|
|
51
|
-
expect(requests.collect
|
|
58
|
+
expect(requests.collect{|request| request.verb.downcase.to_sym}).to match_array([method, method])
|
|
59
|
+
expect(requests.collect{|request| request.uri.to_s}).to match_array([entry_url, users_url])
|
|
52
60
|
end
|
|
61
|
+
|
|
53
62
|
it "merges options" do
|
|
54
63
|
client = Harpy::Client.new :headers => {"Authorization" => "spec"}
|
|
55
64
|
requests = client.send method, [entry_url, users_url], :headers => {"X-Files" => "Harpy"}
|
|
56
65
|
requests.each do |request|
|
|
57
|
-
expect(request.
|
|
66
|
+
expect(request.headers["x-files"]).to eq("Harpy")
|
|
67
|
+
expect(request.headers["authorization"]).to eq("spec")
|
|
58
68
|
end
|
|
59
69
|
end
|
|
60
70
|
end
|
|
61
71
|
end
|
|
62
72
|
end
|
|
73
|
+
|
|
74
|
+
describe "HTTPX request options" do
|
|
75
|
+
it "encodes query parameters" do
|
|
76
|
+
stub_request(:get, "#{users_url}?active=true").to_return(status: 200)
|
|
77
|
+
|
|
78
|
+
response = subject.get(users_url, params: {active: true})
|
|
79
|
+
|
|
80
|
+
expect(response.status).to eq(200)
|
|
81
|
+
expect(WebMock).to have_requested(:get, "#{users_url}?active=true")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "maps username and password to basic authentication" do
|
|
85
|
+
stub_request(:get, entry_url).with(headers: {"Authorization" => "Basic aGFycHk6c3BlYw=="}).to_return(status: 200)
|
|
86
|
+
|
|
87
|
+
response = Harpy::Client.new(username: "harpy", password: "spec").get(entry_url)
|
|
88
|
+
|
|
89
|
+
expect(response.status).to eq(200)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "maps legacy timeout and SSL options" do
|
|
93
|
+
request = Harpy::Client.new(timeout: 5000, ssl_verifypeer: false, ssl_verifyhost: 0).get([entry_url]).first
|
|
94
|
+
|
|
95
|
+
expect(request.options.timeout.total_request_timeout).to eq(5)
|
|
96
|
+
expect(request.options.ssl).to include(verify_mode: OpenSSL::SSL::VERIFY_NONE, verify_hostname: false)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
63
100
|
describe "#run(requests)" do
|
|
64
101
|
before do
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Typhoeus.stub(entry_url, method: :get){ @entry_response }
|
|
69
|
-
Typhoeus.stub(users_url, method: :get){ @users_response }
|
|
102
|
+
stub_request(:get, entry_url).to_return(status: 200, body: "entry")
|
|
103
|
+
stub_request(:get, users_url).to_return(status: 200, body: "users")
|
|
70
104
|
end
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
105
|
+
|
|
106
|
+
it "submits the requests together to HTTPX" do
|
|
107
|
+
requests = subject.get([entry_url, users_url])
|
|
108
|
+
expect(subject.instance_variable_get(:@session)).to receive(:request).with(*requests).and_call_original
|
|
109
|
+
subject.run requests
|
|
74
110
|
end
|
|
111
|
+
|
|
75
112
|
it "returns responses" do
|
|
76
113
|
responses = subject.run subject.get([entry_url, users_url])
|
|
77
|
-
expect(responses).to match_array([
|
|
114
|
+
expect(responses.map(&:body)).to match_array(["entry", "users"])
|
|
78
115
|
end
|
|
79
|
-
|
|
116
|
+
|
|
117
|
+
it "fills in each request response" do
|
|
80
118
|
requests = subject.get([entry_url, users_url])
|
|
81
119
|
subject.run requests
|
|
82
|
-
expect(requests
|
|
83
|
-
expect(requests[1].response).to eq(@users_response)
|
|
120
|
+
expect(requests.map{|request| request.response.body}).to eq(["entry", "users"])
|
|
84
121
|
end
|
|
85
122
|
end
|
|
123
|
+
|
|
86
124
|
describe "#invalid_code(response)" do
|
|
87
125
|
it "raises Harpy::ClientTimeout on request timeout" do
|
|
126
|
+
error = HTTPX::RequestTimeoutError.new(nil, nil, 1)
|
|
88
127
|
expect {
|
|
89
|
-
subject.invalid_code
|
|
128
|
+
subject.invalid_code httpx_error_response(error)
|
|
90
129
|
}.to raise_error Harpy::ClientTimeout
|
|
91
130
|
end
|
|
92
|
-
|
|
131
|
+
|
|
132
|
+
it "raises Harpy::ClientError on transport error" do
|
|
133
|
+
error = HTTPX::ConnectionError.new("Could not connect to server")
|
|
93
134
|
expect {
|
|
94
|
-
subject.invalid_code
|
|
135
|
+
subject.invalid_code httpx_error_response(error)
|
|
95
136
|
}.to raise_error Harpy::ClientError, "Could not connect to server"
|
|
96
137
|
end
|
|
138
|
+
|
|
97
139
|
it "raises Harpy::InvalidResponseCode with code otherwise" do
|
|
98
140
|
expect {
|
|
99
|
-
subject.invalid_code
|
|
141
|
+
subject.invalid_code httpx_response(:code => 404)
|
|
100
142
|
}.to raise_error Harpy::InvalidResponseCode, "404"
|
|
101
143
|
end
|
|
102
144
|
end
|
|
@@ -11,8 +11,8 @@ describe Harpy::EntryPoint do
|
|
|
11
11
|
describe "#resource_url(resource_type)" do
|
|
12
12
|
let(:company_url) { "#{url}/company"}
|
|
13
13
|
let(:json_response) { %Q|{"link": [{"rel": "company", "href": "#{company_url}"}]}| }
|
|
14
|
-
let(:success_response) {
|
|
15
|
-
let(:error_response) {
|
|
14
|
+
let(:success_response) { httpx_response :code => 200, :body => json_response }
|
|
15
|
+
let(:error_response) { httpx_response :code => 500 }
|
|
16
16
|
it "gets entry point from url using client" do
|
|
17
17
|
expect(Harpy.client).to receive(:get).with(url).and_return success_response
|
|
18
18
|
subject.resource_url "user"
|
|
@@ -35,9 +35,9 @@ describe Harpy::EntryPoint do
|
|
|
35
35
|
describe "#urn(urn)" do
|
|
36
36
|
let(:urn) { "urn:harpy:company:1" }
|
|
37
37
|
let(:company_url) { "#{url}/company/1"}
|
|
38
|
-
let(:success_response) {
|
|
39
|
-
let(:error_response) {
|
|
40
|
-
let(:not_found_response) {
|
|
38
|
+
let(:success_response) { httpx_response :code => 301, :headers => "Location: #{company_url}" }
|
|
39
|
+
let(:error_response) { httpx_response :code => 500 }
|
|
40
|
+
let(:not_found_response) { httpx_response :code => 404 }
|
|
41
41
|
it "query remote for this urn using client" do
|
|
42
42
|
expect(Harpy.client).to receive(:get).with("#{url}/#{urn}").and_return success_response
|
|
43
43
|
expect(subject.urn(urn)).to eq(company_url)
|
data/spec/harpy/resource_spec.rb
CHANGED
|
@@ -4,9 +4,8 @@ describe Harpy::Resource do
|
|
|
4
4
|
describe ".from_url(hash)" do
|
|
5
5
|
let(:company_url) { "http://localhost/company/1" }
|
|
6
6
|
let(:json_response) { %Q|{"name": "Harpy ltd", "link": [{"rel": "self", "href": "#{company_url}"}]}| }
|
|
7
|
-
let(:success_response) { Typhoeus::Response.new :code => 200, :body => json_response}
|
|
8
7
|
it "queries multiple resources in parallel and return instances" do
|
|
9
|
-
|
|
8
|
+
stub_request(:get, company_url).to_return(status: 200, body: json_response)
|
|
10
9
|
responses = Harpy::Resource.from_url({ Harpy::Spec::Company => [company_url] })
|
|
11
10
|
expect(responses.size).to eq(1)
|
|
12
11
|
expect(responses[Harpy::Spec::Company].size).to eq(1)
|
|
@@ -59,7 +58,7 @@ describe "class including Harpy::Resource" do
|
|
|
59
58
|
context "called with only one url" do
|
|
60
59
|
let(:url){ "http://localhost/company/1" }
|
|
61
60
|
it "is a properly filled-in instance of Harpy::Spec::Company on success" do
|
|
62
|
-
response =
|
|
61
|
+
response = httpx_response :code => 200, :body => <<-eos
|
|
63
62
|
{
|
|
64
63
|
"name": "Harpy Ltd",
|
|
65
64
|
"link": [
|
|
@@ -74,12 +73,12 @@ describe "class including Harpy::Resource" do
|
|
|
74
73
|
expect(result.link("self")).to eq(url)
|
|
75
74
|
end
|
|
76
75
|
it "is nil when not found" do
|
|
77
|
-
response =
|
|
76
|
+
response = httpx_response :code => 404
|
|
78
77
|
expect(Harpy.client).to receive(:get).with(url).and_return response
|
|
79
78
|
expect(Harpy::Spec::Company.from_url(url)).to be_nil
|
|
80
79
|
end
|
|
81
80
|
it "delegates response code != 200 or 404 to client" do
|
|
82
|
-
response =
|
|
81
|
+
response = httpx_response :code => 500
|
|
83
82
|
expect(Harpy.client).to receive(:get).with(url).and_return response
|
|
84
83
|
expect(Harpy.client).to receive(:invalid_code).with response
|
|
85
84
|
Harpy::Spec::Company.from_url(url)
|
|
@@ -89,7 +88,7 @@ describe "class including Harpy::Resource" do
|
|
|
89
88
|
let(:url1){ "http://localhost/company/1" }
|
|
90
89
|
let(:url2){ "http://localhost/company/2" }
|
|
91
90
|
it "is a properly filled-in instance of Harpy::Spec::Company on success" do
|
|
92
|
-
response1 =
|
|
91
|
+
response1 = httpx_response :code => 200, :body => <<-eos
|
|
93
92
|
{
|
|
94
93
|
"name": "Harpy Ltd",
|
|
95
94
|
"link": [
|
|
@@ -97,7 +96,7 @@ describe "class including Harpy::Resource" do
|
|
|
97
96
|
]
|
|
98
97
|
}
|
|
99
98
|
eos
|
|
100
|
-
response2 =
|
|
99
|
+
response2 = httpx_response :code => 200, :body => <<-eos
|
|
101
100
|
{
|
|
102
101
|
"name": "Harpy Inc",
|
|
103
102
|
"link": [
|
|
@@ -105,8 +104,8 @@ describe "class including Harpy::Resource" do
|
|
|
105
104
|
]
|
|
106
105
|
}
|
|
107
106
|
eos
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
stub_request(:get, url1).to_return(status: 200, body: response1.body.to_s)
|
|
108
|
+
stub_request(:get, url2).to_return(status: 200, body: response2.body.to_s)
|
|
110
109
|
results = Harpy::Spec::Company.from_url [url1, url2]
|
|
111
110
|
expect(results.size).to eq(2)
|
|
112
111
|
expect(results[0]).to be_kind_of Harpy::Spec::Company
|
|
@@ -197,18 +196,18 @@ describe "class including Harpy::Resource" do
|
|
|
197
196
|
context "called with only one url" do
|
|
198
197
|
let(:url){ "http://localhost/company/1" }
|
|
199
198
|
it "is true on success" do
|
|
200
|
-
response =
|
|
199
|
+
response = httpx_response :code => 204
|
|
201
200
|
expect(Harpy.client).to receive(:delete).with(url).and_return response
|
|
202
201
|
result = Harpy::Spec::Company.delete_from_url url
|
|
203
202
|
expect(result).to be_truthy
|
|
204
203
|
end
|
|
205
204
|
it "is false when not found" do
|
|
206
|
-
response =
|
|
205
|
+
response = httpx_response :code => 404
|
|
207
206
|
expect(Harpy.client).to receive(:delete).with(url).and_return response
|
|
208
207
|
expect(Harpy::Spec::Company.delete_from_url(url)).to be_falsey
|
|
209
208
|
end
|
|
210
209
|
it "delegates response code != 204 or 404 to client" do
|
|
211
|
-
response =
|
|
210
|
+
response = httpx_response :code => 500
|
|
212
211
|
expect(Harpy.client).to receive(:delete).with(url).and_return response
|
|
213
212
|
expect(Harpy.client).to receive(:invalid_code).with response
|
|
214
213
|
Harpy::Spec::Company.delete_from_url url
|
|
@@ -309,7 +308,7 @@ describe "class including Harpy::Resource" do
|
|
|
309
308
|
expect(Harpy.entry_point).to receive(:resource_url).with("harpy/spec/company").and_return url
|
|
310
309
|
end
|
|
311
310
|
it "return properly filled instances on 200" do
|
|
312
|
-
response =
|
|
311
|
+
response = httpx_response :code => 200, :body => <<-eos
|
|
313
312
|
{
|
|
314
313
|
"harpy/spec/company": [
|
|
315
314
|
{
|
|
@@ -347,7 +346,7 @@ describe "class including Harpy::Resource" do
|
|
|
347
346
|
expect(companies.blank?).to be true
|
|
348
347
|
end
|
|
349
348
|
it "delegates other response codes to client" do
|
|
350
|
-
response =
|
|
349
|
+
response = httpx_response :code => 500
|
|
351
350
|
expect(Harpy.client).to receive(:get).with(url, :params => {}).and_return response
|
|
352
351
|
expect(Harpy.client).to receive(:invalid_code).with response
|
|
353
352
|
Harpy::Spec::Company.search
|
|
@@ -356,7 +355,7 @@ describe "class including Harpy::Resource" do
|
|
|
356
355
|
describe ".with_url(url)" do
|
|
357
356
|
let(:url){ "http://localhost/user/1/company" }
|
|
358
357
|
it "overrides url used for searches" do
|
|
359
|
-
response =
|
|
358
|
+
response = httpx_response :code => 500
|
|
360
359
|
expect(Harpy.client).to receive(:get).with(url, :params => {}).and_return response
|
|
361
360
|
expect(Harpy.client).to receive(:invalid_code).with response
|
|
362
361
|
Harpy::Spec::Company.with_url(url) do
|
|
@@ -365,7 +364,7 @@ describe "class including Harpy::Resource" do
|
|
|
365
364
|
end
|
|
366
365
|
it "can be nested" do
|
|
367
366
|
url2 = "http://localhost/user/2/company"
|
|
368
|
-
response =
|
|
367
|
+
response = httpx_response :code => 500
|
|
369
368
|
expect(Harpy.client).to receive(:get).ordered.with(url2, :params => {}).and_return response
|
|
370
369
|
expect(Harpy.client).to receive(:get).ordered.with(url, :params => {}).and_return response
|
|
371
370
|
expect(Harpy.client).to receive(:invalid_code).twice.with response
|
|
@@ -492,7 +491,7 @@ describe "class including Harpy::Resource" do
|
|
|
492
491
|
describe "#inspect" do
|
|
493
492
|
subject { Harpy::Spec::Company.new "firstname" => "Anthony" }
|
|
494
493
|
it "shows class name, attributes, errors and persisted state" do
|
|
495
|
-
expect(subject.inspect).to
|
|
494
|
+
expect(subject.inspect).to include("Harpy::Spec::Company", "firstname", "Anthony", "@errors:[] persisted:false>")
|
|
496
495
|
end
|
|
497
496
|
end
|
|
498
497
|
describe "#has_key?(key)" do
|
|
@@ -566,7 +565,7 @@ describe "class including Harpy::Resource" do
|
|
|
566
565
|
end
|
|
567
566
|
[200, 201, 302].each do |response_code|
|
|
568
567
|
it "is true and merges response attributes on #{response_code}" do
|
|
569
|
-
response =
|
|
568
|
+
response = httpx_response :code => response_code, :body => <<-eos
|
|
570
569
|
{
|
|
571
570
|
"firstname": "Anthony",
|
|
572
571
|
"urn": "urn:harpy:user:1",
|
|
@@ -584,19 +583,19 @@ describe "class including Harpy::Resource" do
|
|
|
584
583
|
end
|
|
585
584
|
end
|
|
586
585
|
it "raises Harpy::InvalidResponseCode on 204" do
|
|
587
|
-
response =
|
|
586
|
+
response = httpx_response :code => 204
|
|
588
587
|
expect(Harpy.client).to receive(:post).with(url, :body => body).and_return response
|
|
589
588
|
expect { subject.save }.to raise_error Harpy::InvalidResponseCode, "204"
|
|
590
589
|
expect(subject.callbacks).to match_array([:save, :create])
|
|
591
590
|
end
|
|
592
591
|
it "raises Harpy::Unauthorized on 401" do
|
|
593
|
-
response =
|
|
592
|
+
response = httpx_response :code => 401
|
|
594
593
|
expect(Harpy.client).to receive(:post).with(url, :body => body).and_return response
|
|
595
594
|
expect { subject.save }.to raise_error Harpy::Unauthorized, "Server returned a 401 response code"
|
|
596
595
|
expect(subject.callbacks).to match_array([:save, :create])
|
|
597
596
|
end
|
|
598
597
|
it "is false and fills in errors on 422" do
|
|
599
|
-
response =
|
|
598
|
+
response = httpx_response :code => 422, :body => <<-eos
|
|
600
599
|
{
|
|
601
600
|
"firstname": "Anthony",
|
|
602
601
|
"errors": {
|
|
@@ -613,7 +612,7 @@ describe "class including Harpy::Resource" do
|
|
|
613
612
|
expect(subject.company_name).to eq("Stark Enterprises")
|
|
614
613
|
end
|
|
615
614
|
it "delegates other response codes to client" do
|
|
616
|
-
response =
|
|
615
|
+
response = httpx_response :code => 500
|
|
617
616
|
expect(Harpy.client).to receive(:post).with(url, :body => body).and_return response
|
|
618
617
|
expect(Harpy.client).to receive(:invalid_code).with(response)
|
|
619
618
|
subject.save
|
|
@@ -643,7 +642,7 @@ describe "class including Harpy::Resource" do
|
|
|
643
642
|
end
|
|
644
643
|
[200, 201, 302].each do |response_code|
|
|
645
644
|
it "is true and merges response attributes on #{response_code}" do
|
|
646
|
-
response =
|
|
645
|
+
response = httpx_response :code => response_code, :body => <<-eos
|
|
647
646
|
{
|
|
648
647
|
"firstname": "Anthony",
|
|
649
648
|
"urn": "urn:harpy:user:1",
|
|
@@ -660,20 +659,20 @@ describe "class including Harpy::Resource" do
|
|
|
660
659
|
end
|
|
661
660
|
end
|
|
662
661
|
it "is true but doesn't touch attributes on 204" do
|
|
663
|
-
response =
|
|
662
|
+
response = httpx_response :code => 204
|
|
664
663
|
expect(Harpy.client).to receive(:put).with(url, :body => body).and_return response
|
|
665
664
|
expect(subject.save).to be_truthy
|
|
666
665
|
expect(subject.callbacks).to match_array([:save, :update])
|
|
667
666
|
expect(subject.company_name).to eq("Stark Enterprises")
|
|
668
667
|
end
|
|
669
668
|
it "raises Harpy::Unauthorized on 401" do
|
|
670
|
-
response =
|
|
669
|
+
response = httpx_response :code => 401
|
|
671
670
|
expect(Harpy.client).to receive(:put).with(url, :body => body).and_return response
|
|
672
671
|
expect { subject.save }.to raise_error Harpy::Unauthorized, "Server returned a 401 response code"
|
|
673
672
|
expect(subject.callbacks).to match_array([:save, :update])
|
|
674
673
|
end
|
|
675
674
|
it "is false and fills in errors on 422" do
|
|
676
|
-
response =
|
|
675
|
+
response = httpx_response :code => 422, :body => <<-eos
|
|
677
676
|
{
|
|
678
677
|
"firstname": "Anthony",
|
|
679
678
|
"errors": {
|
|
@@ -690,7 +689,7 @@ describe "class including Harpy::Resource" do
|
|
|
690
689
|
expect(subject.company_name).to eq("Stark Enterprises")
|
|
691
690
|
end
|
|
692
691
|
it "delegates other response codes to client" do
|
|
693
|
-
response =
|
|
692
|
+
response = httpx_response :code => 500
|
|
694
693
|
expect(Harpy.client).to receive(:put).with(url, :body => body).and_return response
|
|
695
694
|
expect(Harpy.client).to receive(:invalid_code).with(response)
|
|
696
695
|
subject.save
|
|
@@ -717,7 +716,7 @@ describe "class including Harpy::Resource" do
|
|
|
717
716
|
end
|
|
718
717
|
[200, 201, 302].each do |response_code|
|
|
719
718
|
it "is true and merges response attributes on #{response_code}" do
|
|
720
|
-
response =
|
|
719
|
+
response = httpx_response :code => response_code, :body => <<-eos
|
|
721
720
|
{
|
|
722
721
|
"firstname": "Anthony",
|
|
723
722
|
"urn": "urn:harpy:user:1",
|
|
@@ -734,20 +733,20 @@ describe "class including Harpy::Resource" do
|
|
|
734
733
|
end
|
|
735
734
|
end
|
|
736
735
|
it "is true but doesn't touch attributes on 204" do
|
|
737
|
-
response =
|
|
736
|
+
response = httpx_response :code => 204
|
|
738
737
|
expect(Harpy.client).to receive(:delete).with(url).and_return response
|
|
739
738
|
expect(subject.destroy).to be_truthy
|
|
740
739
|
expect(subject.callbacks).to match_array([:destroy])
|
|
741
740
|
expect(subject.company_name).to eq("Stark Enterprises")
|
|
742
741
|
end
|
|
743
742
|
it "raises Harpy::Unauthorized on 401" do
|
|
744
|
-
response =
|
|
743
|
+
response = httpx_response :code => 401
|
|
745
744
|
expect(Harpy.client).to receive(:delete).with(url).and_return response
|
|
746
745
|
expect { subject.destroy }.to raise_error Harpy::Unauthorized, "Server returned a 401 response code"
|
|
747
746
|
expect(subject.callbacks).to match_array([:destroy])
|
|
748
747
|
end
|
|
749
748
|
it "is false and fills in errors on 422" do
|
|
750
|
-
response =
|
|
749
|
+
response = httpx_response :code => 422, :body => <<-eos
|
|
751
750
|
{
|
|
752
751
|
"firstname": "Anthony",
|
|
753
752
|
"errors": {
|
|
@@ -764,7 +763,7 @@ describe "class including Harpy::Resource" do
|
|
|
764
763
|
expect(subject.company_name).to eq("Stark Enterprises")
|
|
765
764
|
end
|
|
766
765
|
it "delegates other response codes to client" do
|
|
767
|
-
response =
|
|
766
|
+
response = httpx_response :code => 500
|
|
768
767
|
expect(Harpy.client).to receive(:delete).with(url).and_return response
|
|
769
768
|
expect(Harpy.client).to receive(:invalid_code).with(response)
|
|
770
769
|
subject.destroy
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
|
+
require "webmock/rspec"
|
|
2
|
+
require "httpx"
|
|
1
3
|
require "harpy"
|
|
2
|
-
|
|
4
|
+
|
|
5
|
+
WebMock.disable_net_connect!
|
|
3
6
|
|
|
4
7
|
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each{|f| require f}
|
|
5
8
|
|
|
9
|
+
module HTTPXSpecHelpers
|
|
10
|
+
def httpx_response(options)
|
|
11
|
+
session = HTTPX::Session.new
|
|
12
|
+
request = session.build_request("GET", "http://localhost")
|
|
13
|
+
headers = options[:headers] || {}
|
|
14
|
+
if headers.is_a?(String)
|
|
15
|
+
headers = headers.lines.each_with_object({}) do |line, parsed|
|
|
16
|
+
name, value = line.split(":", 2)
|
|
17
|
+
parsed[name.strip] = value.strip if value
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
response = HTTPX::Response.new(request, options[:code], "1.1", headers)
|
|
22
|
+
response << options[:body] if options[:body]
|
|
23
|
+
response.finish!
|
|
24
|
+
response
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def httpx_error_response(error)
|
|
28
|
+
session = HTTPX::Session.new
|
|
29
|
+
request = session.build_request("GET", "http://localhost")
|
|
30
|
+
HTTPX::ErrorResponse.new(request, error)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
6
34
|
RSpec.configure do |config|
|
|
35
|
+
config.include HTTPXSpecHelpers
|
|
7
36
|
config.after(:each) do
|
|
8
|
-
|
|
37
|
+
WebMock.reset!
|
|
9
38
|
end
|
|
10
39
|
end
|
metadata
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: harpy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0.preview1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joseph HALTER
|
|
8
8
|
- Jonathan TRON
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
|
-
name:
|
|
14
|
+
name: httpx
|
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
|
17
16
|
requirements:
|
|
18
17
|
- - ">="
|
|
19
18
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: 0
|
|
19
|
+
version: '1.0'
|
|
21
20
|
type: :runtime
|
|
22
21
|
prerelease: false
|
|
23
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
23
|
requirements:
|
|
25
24
|
- - ">="
|
|
26
25
|
- !ruby/object:Gem::Version
|
|
27
|
-
version: 0
|
|
26
|
+
version: '1.0'
|
|
28
27
|
- !ruby/object:Gem::Dependency
|
|
29
28
|
name: activesupport
|
|
30
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -95,6 +94,20 @@ dependencies:
|
|
|
95
94
|
- - ">="
|
|
96
95
|
- !ruby/object:Gem::Version
|
|
97
96
|
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: webmock
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
98
111
|
description: Client for REST API with HATEOAS
|
|
99
112
|
email:
|
|
100
113
|
- joseph.halter@thetalentbox.com
|
|
@@ -103,9 +116,10 @@ executables: []
|
|
|
103
116
|
extensions: []
|
|
104
117
|
extra_rdoc_files: []
|
|
105
118
|
files:
|
|
119
|
+
- ".forgejo/workflows/test.yml"
|
|
120
|
+
- ".github/workflows/test.yml"
|
|
106
121
|
- ".gitignore"
|
|
107
122
|
- ".rspec"
|
|
108
|
-
- ".travis.yml"
|
|
109
123
|
- Gemfile
|
|
110
124
|
- LICENSE
|
|
111
125
|
- README.md
|
|
@@ -127,7 +141,6 @@ homepage: https://github.com/TalentBox/harpy
|
|
|
127
141
|
licenses:
|
|
128
142
|
- MIT
|
|
129
143
|
metadata: {}
|
|
130
|
-
post_install_message:
|
|
131
144
|
rdoc_options: []
|
|
132
145
|
require_paths:
|
|
133
146
|
- lib
|
|
@@ -135,15 +148,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
135
148
|
requirements:
|
|
136
149
|
- - ">="
|
|
137
150
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '
|
|
151
|
+
version: '3.4'
|
|
139
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
153
|
requirements:
|
|
141
154
|
- - ">="
|
|
142
155
|
- !ruby/object:Gem::Version
|
|
143
156
|
version: '0'
|
|
144
157
|
requirements: []
|
|
145
|
-
rubygems_version:
|
|
146
|
-
signing_key:
|
|
158
|
+
rubygems_version: 4.0.23
|
|
147
159
|
specification_version: 4
|
|
148
160
|
summary: Client for REST API
|
|
149
161
|
test_files:
|