ebay-api-ruby 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +162 -0
- data/lib/ebay/base.rb +11 -0
- data/lib/ebay/browse.rb +23 -0
- data/lib/ebay/client.rb +116 -0
- data/lib/ebay/configuration.rb +30 -0
- data/lib/ebay/errors.rb +51 -0
- data/lib/ebay/finding.rb +42 -0
- data/lib/ebay/taxonomy.rb +18 -0
- data/lib/ebay/version.rb +5 -0
- data/lib/ebay.rb +34 -0
- data/spec/ebay/browse_spec.rb +60 -0
- data/spec/ebay/client_spec.rb +125 -0
- data/spec/ebay/configuration_spec.rb +74 -0
- data/spec/ebay/finding_spec.rb +78 -0
- data/spec/ebay/taxonomy_spec.rb +49 -0
- data/spec/examples.txt +43 -0
- data/spec/spec_helper.rb +55 -0
- metadata +192 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 282125c3f9f1addb39262ff57f5d817264dee5d8e39e00b22ede983299424ae5
|
|
4
|
+
data.tar.gz: 8cfabcedcccfd2ee3c83a1fb0437e2059119b11676249df6c233b972f2ea596d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c1a0b61cbaa0cba58e3b2abc036f7e378f46663a8daaf528ec730a42198bd675ccd265a26482993cd868b2d16aa1d0507a9a13d3c89eb408df52ae10c18fb6d9
|
|
7
|
+
data.tar.gz: 516284188e67e58bdbb7b6ab430d6903bac9c2075398a061a1a8342d9075d1d9d1aa5a7892f1b59a568891ff3401c32be0497b518250f68f4ba7fc6b865b6e46
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-03-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Initial release
|
|
10
|
+
- OAuth 2.0 Client Credentials authentication with token caching
|
|
11
|
+
- Browse API: search, get_item, get_items_by_item_group, search_by_image
|
|
12
|
+
- Finding API: findItemsByKeywords, findCompletedItems, findItemsAdvanced, findItemsByCategory
|
|
13
|
+
- Taxonomy API: get_default_category_tree_id, get_category_tree, get_category_subtree
|
|
14
|
+
- Sandbox mode support
|
|
15
|
+
- Full RSpec test suite with WebMock
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eduardo Souza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# ebay-api-ruby
|
|
2
|
+
|
|
3
|
+
A modern Ruby gem for the eBay REST APIs. Supports the Browse API, Finding API, and Taxonomy API with OAuth 2.0 authentication.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'ebay-api-ruby'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install it yourself:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install ebay-api-ruby
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require 'ebay'
|
|
29
|
+
|
|
30
|
+
Ebay.configure do |config|
|
|
31
|
+
config.client_id = ENV['EBAY_CLIENT_ID']
|
|
32
|
+
config.client_secret = ENV['EBAY_CLIENT_SECRET']
|
|
33
|
+
config.app_id = ENV['EBAY_APP_ID'] # Optional, defaults to client_id for Finding API
|
|
34
|
+
config.marketplace_id = 'EBAY_US' # Default
|
|
35
|
+
config.timeout = 30 # Default
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Sandbox Mode
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
Ebay.configure do |config|
|
|
43
|
+
config.client_id = ENV['EBAY_SANDBOX_CLIENT_ID']
|
|
44
|
+
config.client_secret = ENV['EBAY_SANDBOX_CLIENT_SECRET']
|
|
45
|
+
config.sandbox!
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### Browse API
|
|
52
|
+
|
|
53
|
+
Search for items, get item details, and search by image.
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
browse = Ebay::Browse.new
|
|
57
|
+
|
|
58
|
+
# Search items
|
|
59
|
+
results = browse.search(q: "vintage rolex", limit: 10)
|
|
60
|
+
results['itemSummaries'].each do |item|
|
|
61
|
+
puts "#{item['title']} - #{item['price']['value']} #{item['price']['currency']}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get a specific item
|
|
65
|
+
item = browse.get_item("v1|123456789|0")
|
|
66
|
+
|
|
67
|
+
# Get items by group
|
|
68
|
+
items = browse.get_items_by_item_group("group123")
|
|
69
|
+
|
|
70
|
+
# Search by image
|
|
71
|
+
results = browse.search_by_image(Base64.encode64(File.read("watch.jpg")))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Finding API
|
|
75
|
+
|
|
76
|
+
Search active and completed/sold listings. Uses the legacy Finding Service (no OAuth required, just app_id).
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
finding = Ebay::Finding.new
|
|
80
|
+
|
|
81
|
+
# Search by keywords
|
|
82
|
+
results = finding.find_items_by_keywords("macbook pro m3")
|
|
83
|
+
|
|
84
|
+
# Find completed/sold items (great for price history)
|
|
85
|
+
sold = finding.find_completed_items("pokemon base set charizard")
|
|
86
|
+
|
|
87
|
+
# Advanced search with filters
|
|
88
|
+
results = finding.find_items_advanced(keywords: "nike jordan", categoryId: "93427")
|
|
89
|
+
|
|
90
|
+
# Search by category
|
|
91
|
+
results = finding.find_items_by_category("9355")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Taxonomy API
|
|
95
|
+
|
|
96
|
+
Get category trees and browse category hierarchies.
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
taxonomy = Ebay::Taxonomy.new
|
|
100
|
+
|
|
101
|
+
# Get default category tree ID for a marketplace
|
|
102
|
+
tree_info = taxonomy.get_default_category_tree_id("EBAY_US")
|
|
103
|
+
tree_id = tree_info['categoryTreeId']
|
|
104
|
+
|
|
105
|
+
# Get the full category tree
|
|
106
|
+
tree = taxonomy.get_category_tree(tree_id)
|
|
107
|
+
|
|
108
|
+
# Get a subtree
|
|
109
|
+
subtree = taxonomy.get_category_subtree(tree_id, "9355")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Authentication
|
|
113
|
+
|
|
114
|
+
The gem handles OAuth 2.0 Client Credentials authentication automatically. Tokens are cached and refreshed when expired.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
client = Ebay.client
|
|
118
|
+
|
|
119
|
+
# Manual authentication (usually not needed)
|
|
120
|
+
client.authenticate!
|
|
121
|
+
|
|
122
|
+
# Direct API calls
|
|
123
|
+
result = client.get("/buy/browse/v1/item_summary/search", q: "test")
|
|
124
|
+
result = client.post("/some/endpoint", { data: "value" })
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Error Handling
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
begin
|
|
131
|
+
browse.search(q: "test")
|
|
132
|
+
rescue Ebay::AuthenticationError => e
|
|
133
|
+
puts "Auth failed: #{e.message}"
|
|
134
|
+
rescue Ebay::NotFoundError => e
|
|
135
|
+
puts "Not found: #{e.message} (#{e.status_code})"
|
|
136
|
+
rescue Ebay::RateLimitError => e
|
|
137
|
+
puts "Rate limited. Retry after: #{e.retry_after}"
|
|
138
|
+
rescue Ebay::ValidationError => e
|
|
139
|
+
puts "Validation error: #{e.errors}"
|
|
140
|
+
rescue Ebay::APIError => e
|
|
141
|
+
puts "API error: #{e.message} (#{e.status_code})"
|
|
142
|
+
rescue Ebay::ConfigurationError => e
|
|
143
|
+
puts "Config error: #{e.message}"
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
git clone https://github.com/esouza/ebay-api-ruby.git
|
|
151
|
+
cd ebay-api-ruby
|
|
152
|
+
bundle install
|
|
153
|
+
bundle exec rspec
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Contributing
|
|
157
|
+
|
|
158
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/esouza/ebay-api-ruby. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
data/lib/ebay/base.rb
ADDED
data/lib/ebay/browse.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module Ebay
|
|
6
|
+
class Browse < Base
|
|
7
|
+
def search(q:, **params)
|
|
8
|
+
client.get("/buy/browse/v1/item_summary/search", { q: q }.merge(params))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get_item(item_id)
|
|
12
|
+
client.get("/buy/browse/v1/item/#{URI.encode_www_form_component(item_id)}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get_items_by_item_group(item_group_id)
|
|
16
|
+
client.get("/buy/browse/v1/item/get_items_by_item_group", item_group_id: item_group_id)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def search_by_image(image_base64, **params)
|
|
20
|
+
client.post("/buy/browse/v1/item_summary/search_by_image", { image: image_base64 }.merge(params))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/ebay/client.rb
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ebay
|
|
4
|
+
class Client
|
|
5
|
+
include HTTParty
|
|
6
|
+
|
|
7
|
+
attr_reader :configuration
|
|
8
|
+
|
|
9
|
+
def initialize(configuration = nil)
|
|
10
|
+
@configuration = configuration || Ebay.configuration
|
|
11
|
+
@access_token = nil
|
|
12
|
+
@token_expires_at = nil
|
|
13
|
+
validate_configuration!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def authenticate!
|
|
17
|
+
credentials = Base64.strict_encode64("#{configuration.client_id}:#{configuration.client_secret}")
|
|
18
|
+
|
|
19
|
+
response = self.class.post(
|
|
20
|
+
"#{configuration.base_url}/identity/v1/oauth2/token",
|
|
21
|
+
headers: {
|
|
22
|
+
'Authorization' => "Basic #{credentials}",
|
|
23
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
|
24
|
+
},
|
|
25
|
+
body: "grant_type=client_credentials&scope=https://api.ebay.com/oauth/api_scope",
|
|
26
|
+
timeout: configuration.timeout
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
case response.code
|
|
30
|
+
when 200
|
|
31
|
+
data = response.parsed_response
|
|
32
|
+
@access_token = data['access_token']
|
|
33
|
+
@token_expires_at = Time.now + (data['expires_in'] || 7200).to_i - 60
|
|
34
|
+
@access_token
|
|
35
|
+
else
|
|
36
|
+
raise AuthenticationError, "OAuth token request failed: #{response.body}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get(path, params = {})
|
|
41
|
+
request(:get, path, query: params)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def post(path, data = {})
|
|
45
|
+
request(:post, path, body: data.to_json)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def validate_configuration!
|
|
51
|
+
raise ConfigurationError unless configuration&.valid?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ensure_authenticated!
|
|
55
|
+
if @access_token.nil? || @token_expires_at.nil? || Time.now >= @token_expires_at
|
|
56
|
+
authenticate!
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def request(method, path, options = {})
|
|
61
|
+
ensure_authenticated!
|
|
62
|
+
|
|
63
|
+
url = "#{configuration.base_url}#{path}"
|
|
64
|
+
request_options = {
|
|
65
|
+
headers: default_headers,
|
|
66
|
+
timeout: configuration.timeout
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if options[:query] && !options[:query].empty?
|
|
70
|
+
request_options[:query] = options[:query]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if options[:body]
|
|
74
|
+
request_options[:body] = options[:body]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
response = self.class.send(method, url, request_options)
|
|
78
|
+
handle_response(response)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def default_headers
|
|
82
|
+
{
|
|
83
|
+
'Authorization' => "Bearer #{@access_token}",
|
|
84
|
+
'Content-Type' => 'application/json',
|
|
85
|
+
'Accept' => 'application/json'
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def handle_response(response)
|
|
90
|
+
case response.code
|
|
91
|
+
when 200, 201, 202, 204
|
|
92
|
+
response.parsed_response
|
|
93
|
+
when 401
|
|
94
|
+
raise AuthenticationError, "Authentication failed: #{response.body}"
|
|
95
|
+
when 404
|
|
96
|
+
raise NotFoundError.new("Resource not found", response.code, response.body)
|
|
97
|
+
when 429
|
|
98
|
+
retry_after = response.headers['retry-after']
|
|
99
|
+
raise RateLimitError.new("Rate limit exceeded", response.code, response.body, retry_after)
|
|
100
|
+
when 400, 422
|
|
101
|
+
parsed = response.parsed_response || {}
|
|
102
|
+
errors = parsed['errors'] || parsed['message'] || {}
|
|
103
|
+
raise ValidationError.new(
|
|
104
|
+
parsed['message'] || 'Validation failed',
|
|
105
|
+
response.code,
|
|
106
|
+
response.body,
|
|
107
|
+
errors
|
|
108
|
+
)
|
|
109
|
+
when 500..599
|
|
110
|
+
raise APIError.new("Server error", response.code, response.body)
|
|
111
|
+
else
|
|
112
|
+
raise APIError.new("Unexpected response", response.code, response.body)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ebay
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :client_id, :client_secret, :app_id,
|
|
6
|
+
:base_url, :sandbox, :timeout, :marketplace_id
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@base_url = "https://api.ebay.com"
|
|
10
|
+
@sandbox = false
|
|
11
|
+
@timeout = 30
|
|
12
|
+
@marketplace_id = "EBAY_US"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def sandbox!
|
|
16
|
+
@sandbox = true
|
|
17
|
+
@base_url = "https://api.sandbox.ebay.com"
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def valid?
|
|
22
|
+
!client_id.nil? && !client_id.empty? &&
|
|
23
|
+
!client_secret.nil? && !client_secret.empty?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def finding_url
|
|
27
|
+
sandbox ? "https://svcs.sandbox.ebay.com/services/search/FindingService/v1" : "https://svcs.ebay.com/services/search/FindingService/v1"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/ebay/errors.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ebay
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class ConfigurationError < Error
|
|
7
|
+
def initialize(message = "Missing required configuration: client_id and client_secret")
|
|
8
|
+
super(message)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class AuthenticationError < Error
|
|
13
|
+
def initialize(message = "Authentication failed")
|
|
14
|
+
super(message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class APIError < Error
|
|
19
|
+
attr_reader :status_code, :response_body
|
|
20
|
+
|
|
21
|
+
def initialize(message, status_code = nil, response_body = nil)
|
|
22
|
+
super(message)
|
|
23
|
+
@status_code = status_code
|
|
24
|
+
@response_body = response_body
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ValidationError < APIError
|
|
29
|
+
attr_reader :errors
|
|
30
|
+
|
|
31
|
+
def initialize(message, status_code = nil, response_body = nil, errors = {})
|
|
32
|
+
super(message, status_code, response_body)
|
|
33
|
+
@errors = errors
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class NotFoundError < APIError
|
|
38
|
+
def initialize(message = "Resource not found", status_code = 404, response_body = nil)
|
|
39
|
+
super(message, status_code, response_body)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class RateLimitError < APIError
|
|
44
|
+
attr_reader :retry_after
|
|
45
|
+
|
|
46
|
+
def initialize(message = "Rate limit exceeded", status_code = 429, response_body = nil, retry_after = nil)
|
|
47
|
+
super(message, status_code, response_body)
|
|
48
|
+
@retry_after = retry_after
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/ebay/finding.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ebay
|
|
4
|
+
class Finding < Base
|
|
5
|
+
def find_items_by_keywords(keywords, **params)
|
|
6
|
+
finding_request("findItemsByKeywords", { keywords: keywords }.merge(params))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def find_completed_items(keywords, **params)
|
|
10
|
+
finding_request("findCompletedItems", { keywords: keywords }.merge(params))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def find_items_advanced(**params)
|
|
14
|
+
finding_request("findItemsAdvanced", params)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find_items_by_category(category_id, **params)
|
|
18
|
+
finding_request("findItemsByCategory", { categoryId: category_id }.merge(params))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def finding_request(operation, params)
|
|
24
|
+
url = client.configuration.finding_url
|
|
25
|
+
query = {
|
|
26
|
+
"OPERATION-NAME" => operation,
|
|
27
|
+
"SERVICE-VERSION" => "1.0.0",
|
|
28
|
+
"SECURITY-APPNAME" => client.configuration.app_id || client.configuration.client_id,
|
|
29
|
+
"RESPONSE-DATA-FORMAT" => "JSON",
|
|
30
|
+
"REST-PAYLOAD" => ""
|
|
31
|
+
}.merge(params)
|
|
32
|
+
|
|
33
|
+
response = HTTParty.get(url, query: query, timeout: client.configuration.timeout)
|
|
34
|
+
handle_finding_response(response, operation)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def handle_finding_response(response, operation)
|
|
38
|
+
return response.parsed_response unless response.code >= 400
|
|
39
|
+
raise APIError.new("Finding API error", response.code, response.body)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ebay
|
|
4
|
+
class Taxonomy < Base
|
|
5
|
+
def get_default_category_tree_id(marketplace_id = nil)
|
|
6
|
+
mid = marketplace_id || client.configuration.marketplace_id
|
|
7
|
+
client.get("/commerce/taxonomy/v1/get_default_category_tree_id", marketplace_id: mid)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def get_category_tree(category_tree_id)
|
|
11
|
+
client.get("/commerce/taxonomy/v1/category_tree/#{category_tree_id}")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get_category_subtree(category_tree_id, category_id)
|
|
15
|
+
client.get("/commerce/taxonomy/v1/category_tree/#{category_tree_id}/get_category_subtree", category_id: category_id)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/ebay/version.rb
ADDED
data/lib/ebay.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'httparty'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'base64'
|
|
6
|
+
|
|
7
|
+
require_relative 'ebay/version'
|
|
8
|
+
require_relative 'ebay/errors'
|
|
9
|
+
require_relative 'ebay/configuration'
|
|
10
|
+
require_relative 'ebay/client'
|
|
11
|
+
require_relative 'ebay/base'
|
|
12
|
+
require_relative 'ebay/browse'
|
|
13
|
+
require_relative 'ebay/finding'
|
|
14
|
+
require_relative 'ebay/taxonomy'
|
|
15
|
+
|
|
16
|
+
module Ebay
|
|
17
|
+
class << self
|
|
18
|
+
attr_accessor :configuration
|
|
19
|
+
|
|
20
|
+
def configure
|
|
21
|
+
self.configuration ||= Configuration.new
|
|
22
|
+
yield(configuration) if block_given?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def client
|
|
26
|
+
@client ||= Client.new(configuration)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reset!
|
|
30
|
+
@client = nil
|
|
31
|
+
@configuration = nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Ebay::Browse do
|
|
4
|
+
let(:client) { Ebay.client }
|
|
5
|
+
let(:browse) { described_class.new(client) }
|
|
6
|
+
|
|
7
|
+
before { stub_oauth_token }
|
|
8
|
+
|
|
9
|
+
describe "#search" do
|
|
10
|
+
it "searches items by keyword" do
|
|
11
|
+
stub_request(:get, "https://api.ebay.com/buy/browse/v1/item_summary/search")
|
|
12
|
+
.with(query: hash_including(q: "iphone"))
|
|
13
|
+
.to_return(status: 200, body: '{"itemSummaries": []}', headers: { 'Content-Type' => 'application/json' })
|
|
14
|
+
|
|
15
|
+
result = browse.search(q: "iphone")
|
|
16
|
+
expect(result).to eq("itemSummaries" => [])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "passes additional params" do
|
|
20
|
+
stub_request(:get, "https://api.ebay.com/buy/browse/v1/item_summary/search")
|
|
21
|
+
.with(query: hash_including(q: "iphone", limit: "10"))
|
|
22
|
+
.to_return(status: 200, body: '{"itemSummaries": []}', headers: { 'Content-Type' => 'application/json' })
|
|
23
|
+
|
|
24
|
+
browse.search(q: "iphone", limit: "10")
|
|
25
|
+
expect(WebMock).to have_requested(:get, "https://api.ebay.com/buy/browse/v1/item_summary/search")
|
|
26
|
+
.with(query: hash_including(q: "iphone", limit: "10"))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "#get_item" do
|
|
31
|
+
it "retrieves a single item" do
|
|
32
|
+
stub_request(:get, "https://api.ebay.com/buy/browse/v1/item/v1%7C123%7C0")
|
|
33
|
+
.to_return(status: 200, body: '{"itemId": "v1|123|0"}', headers: { 'Content-Type' => 'application/json' })
|
|
34
|
+
|
|
35
|
+
result = browse.get_item("v1|123|0")
|
|
36
|
+
expect(result).to eq("itemId" => "v1|123|0")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "#get_items_by_item_group" do
|
|
41
|
+
it "retrieves items by group" do
|
|
42
|
+
stub_request(:get, "https://api.ebay.com/buy/browse/v1/item/get_items_by_item_group")
|
|
43
|
+
.with(query: { item_group_id: "group123" })
|
|
44
|
+
.to_return(status: 200, body: '{"items": []}', headers: { 'Content-Type' => 'application/json' })
|
|
45
|
+
|
|
46
|
+
result = browse.get_items_by_item_group("group123")
|
|
47
|
+
expect(result).to eq("items" => [])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe "#search_by_image" do
|
|
52
|
+
it "searches by image" do
|
|
53
|
+
stub_request(:post, "https://api.ebay.com/buy/browse/v1/item_summary/search_by_image")
|
|
54
|
+
.to_return(status: 200, body: '{"itemSummaries": []}', headers: { 'Content-Type' => 'application/json' })
|
|
55
|
+
|
|
56
|
+
result = browse.search_by_image("base64data")
|
|
57
|
+
expect(result).to eq("itemSummaries" => [])
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Ebay::Client do
|
|
4
|
+
let(:client) { Ebay.client }
|
|
5
|
+
|
|
6
|
+
describe "#initialize" do
|
|
7
|
+
it "raises ConfigurationError without valid config" do
|
|
8
|
+
Ebay.reset!
|
|
9
|
+
Ebay.configure { |c| c.client_id = nil }
|
|
10
|
+
expect { Ebay.client }.to raise_error(Ebay::ConfigurationError)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe "#authenticate!" do
|
|
15
|
+
it "obtains an access token" do
|
|
16
|
+
stub_oauth_token
|
|
17
|
+
token = client.authenticate!
|
|
18
|
+
expect(token).to eq("test_token")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "sends correct authorization header" do
|
|
22
|
+
stub_oauth_token
|
|
23
|
+
client.authenticate!
|
|
24
|
+
|
|
25
|
+
expected_credentials = Base64.strict_encode64("test_client_id:test_client_secret")
|
|
26
|
+
expect(WebMock).to have_requested(:post, "https://api.ebay.com/identity/v1/oauth2/token")
|
|
27
|
+
.with(headers: { 'Authorization' => "Basic #{expected_credentials}" })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "raises AuthenticationError on failure" do
|
|
31
|
+
stub_request(:post, "https://api.ebay.com/identity/v1/oauth2/token")
|
|
32
|
+
.to_return(status: 401, body: "Unauthorized")
|
|
33
|
+
|
|
34
|
+
expect { client.authenticate! }.to raise_error(Ebay::AuthenticationError)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "#get" do
|
|
39
|
+
before { stub_oauth_token }
|
|
40
|
+
|
|
41
|
+
it "makes authenticated GET requests" do
|
|
42
|
+
stub_request(:get, "https://api.ebay.com/test")
|
|
43
|
+
.to_return(status: 200, body: '{"result": "ok"}', headers: { 'Content-Type' => 'application/json' })
|
|
44
|
+
|
|
45
|
+
result = client.get("/test")
|
|
46
|
+
expect(result).to eq("result" => "ok")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "passes query params" do
|
|
50
|
+
stub_request(:get, "https://api.ebay.com/test?q=shoes")
|
|
51
|
+
.to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
|
52
|
+
|
|
53
|
+
client.get("/test", q: "shoes")
|
|
54
|
+
expect(WebMock).to have_requested(:get, "https://api.ebay.com/test").with(query: { q: "shoes" })
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "auto-authenticates before request" do
|
|
58
|
+
stub_request(:get, "https://api.ebay.com/test")
|
|
59
|
+
.to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
|
60
|
+
|
|
61
|
+
client.get("/test")
|
|
62
|
+
expect(WebMock).to have_requested(:post, "https://api.ebay.com/identity/v1/oauth2/token").once
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe "#post" do
|
|
67
|
+
before { stub_oauth_token }
|
|
68
|
+
|
|
69
|
+
it "makes authenticated POST requests" do
|
|
70
|
+
stub_request(:post, "https://api.ebay.com/test")
|
|
71
|
+
.to_return(status: 200, body: '{"created": true}', headers: { 'Content-Type' => 'application/json' })
|
|
72
|
+
|
|
73
|
+
result = client.post("/test", { name: "item" })
|
|
74
|
+
expect(result).to eq("created" => true)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe "error handling" do
|
|
79
|
+
before { stub_oauth_token }
|
|
80
|
+
|
|
81
|
+
it "raises NotFoundError on 404" do
|
|
82
|
+
stub_request(:get, "https://api.ebay.com/test")
|
|
83
|
+
.to_return(status: 404, body: "Not Found")
|
|
84
|
+
|
|
85
|
+
expect { client.get("/test") }.to raise_error(Ebay::NotFoundError)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "raises RateLimitError on 429" do
|
|
89
|
+
stub_request(:get, "https://api.ebay.com/test")
|
|
90
|
+
.to_return(status: 429, body: "Too Many Requests", headers: { 'retry-after' => '60' })
|
|
91
|
+
|
|
92
|
+
expect { client.get("/test") }.to raise_error(Ebay::RateLimitError) do |error|
|
|
93
|
+
expect(error.retry_after).to eq('60')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "raises ValidationError on 400" do
|
|
98
|
+
stub_request(:get, "https://api.ebay.com/test")
|
|
99
|
+
.to_return(status: 400, body: '{"message": "Bad Request"}', headers: { 'Content-Type' => 'application/json' })
|
|
100
|
+
|
|
101
|
+
expect { client.get("/test") }.to raise_error(Ebay::ValidationError)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "raises APIError on 500" do
|
|
105
|
+
stub_request(:get, "https://api.ebay.com/test")
|
|
106
|
+
.to_return(status: 500, body: "Internal Server Error")
|
|
107
|
+
|
|
108
|
+
expect { client.get("/test") }.to raise_error(Ebay::APIError)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe "token caching" do
|
|
113
|
+
it "reuses token for multiple requests" do
|
|
114
|
+
stub_oauth_token
|
|
115
|
+
|
|
116
|
+
stub_request(:get, "https://api.ebay.com/test")
|
|
117
|
+
.to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
|
118
|
+
|
|
119
|
+
client.get("/test")
|
|
120
|
+
client.get("/test")
|
|
121
|
+
|
|
122
|
+
expect(WebMock).to have_requested(:post, "https://api.ebay.com/identity/v1/oauth2/token").once
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Ebay::Configuration do
|
|
4
|
+
subject(:config) { described_class.new }
|
|
5
|
+
|
|
6
|
+
describe "#initialize" do
|
|
7
|
+
it "sets default base_url" do
|
|
8
|
+
expect(config.base_url).to eq("https://api.ebay.com")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "sets default sandbox to false" do
|
|
12
|
+
expect(config.sandbox).to be false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "sets default timeout to 30" do
|
|
16
|
+
expect(config.timeout).to eq(30)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "sets default marketplace_id" do
|
|
20
|
+
expect(config.marketplace_id).to eq("EBAY_US")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "#sandbox!" do
|
|
25
|
+
it "enables sandbox mode" do
|
|
26
|
+
config.sandbox!
|
|
27
|
+
expect(config.sandbox).to be true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "changes base_url to sandbox" do
|
|
31
|
+
config.sandbox!
|
|
32
|
+
expect(config.base_url).to eq("https://api.sandbox.ebay.com")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "returns self for chaining" do
|
|
36
|
+
expect(config.sandbox!).to eq(config)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "#valid?" do
|
|
41
|
+
it "returns false when client_id is nil" do
|
|
42
|
+
config.client_secret = "secret"
|
|
43
|
+
expect(config.valid?).to be false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "returns false when client_secret is nil" do
|
|
47
|
+
config.client_id = "id"
|
|
48
|
+
expect(config.valid?).to be false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "returns false when client_id is empty" do
|
|
52
|
+
config.client_id = ""
|
|
53
|
+
config.client_secret = "secret"
|
|
54
|
+
expect(config.valid?).to be false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "returns true when both are set" do
|
|
58
|
+
config.client_id = "id"
|
|
59
|
+
config.client_secret = "secret"
|
|
60
|
+
expect(config.valid?).to be true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "#finding_url" do
|
|
65
|
+
it "returns production URL by default" do
|
|
66
|
+
expect(config.finding_url).to eq("https://svcs.ebay.com/services/search/FindingService/v1")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "returns sandbox URL when sandbox enabled" do
|
|
70
|
+
config.sandbox!
|
|
71
|
+
expect(config.finding_url).to eq("https://svcs.sandbox.ebay.com/services/search/FindingService/v1")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Ebay::Finding do
|
|
4
|
+
let(:client) { Ebay.client }
|
|
5
|
+
let(:finding) { described_class.new(client) }
|
|
6
|
+
let(:finding_url) { "https://svcs.ebay.com/services/search/FindingService/v1" }
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
stub_oauth_token
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "#find_items_by_keywords" do
|
|
13
|
+
it "searches by keywords" do
|
|
14
|
+
stub_request(:get, finding_url)
|
|
15
|
+
.with(query: hash_including("OPERATION-NAME" => "findItemsByKeywords", "keywords" => "laptop"))
|
|
16
|
+
.to_return(status: 200, body: '{"findItemsByKeywordsResponse": []}', headers: { 'Content-Type' => 'application/json' })
|
|
17
|
+
|
|
18
|
+
result = finding.find_items_by_keywords("laptop")
|
|
19
|
+
expect(result).to eq("findItemsByKeywordsResponse" => [])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "includes correct service params" do
|
|
23
|
+
stub_request(:get, finding_url)
|
|
24
|
+
.with(query: hash_including(
|
|
25
|
+
"OPERATION-NAME" => "findItemsByKeywords",
|
|
26
|
+
"SERVICE-VERSION" => "1.0.0",
|
|
27
|
+
"SECURITY-APPNAME" => "test_app_id",
|
|
28
|
+
"RESPONSE-DATA-FORMAT" => "JSON"
|
|
29
|
+
))
|
|
30
|
+
.to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
|
31
|
+
|
|
32
|
+
finding.find_items_by_keywords("test")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe "#find_completed_items" do
|
|
37
|
+
it "searches completed/sold items" do
|
|
38
|
+
stub_request(:get, finding_url)
|
|
39
|
+
.with(query: hash_including("OPERATION-NAME" => "findCompletedItems", "keywords" => "vintage watch"))
|
|
40
|
+
.to_return(status: 200, body: '{"findCompletedItemsResponse": []}', headers: { 'Content-Type' => 'application/json' })
|
|
41
|
+
|
|
42
|
+
result = finding.find_completed_items("vintage watch")
|
|
43
|
+
expect(result).to eq("findCompletedItemsResponse" => [])
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe "#find_items_advanced" do
|
|
48
|
+
it "performs advanced search" do
|
|
49
|
+
stub_request(:get, finding_url)
|
|
50
|
+
.with(query: hash_including("OPERATION-NAME" => "findItemsAdvanced"))
|
|
51
|
+
.to_return(status: 200, body: '{"findItemsAdvancedResponse": []}', headers: { 'Content-Type' => 'application/json' })
|
|
52
|
+
|
|
53
|
+
result = finding.find_items_advanced(keywords: "shoes", categoryId: "3034")
|
|
54
|
+
expect(result).to eq("findItemsAdvancedResponse" => [])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "#find_items_by_category" do
|
|
59
|
+
it "searches by category" do
|
|
60
|
+
stub_request(:get, finding_url)
|
|
61
|
+
.with(query: hash_including("OPERATION-NAME" => "findItemsByCategory", "categoryId" => "9355"))
|
|
62
|
+
.to_return(status: 200, body: '{"findItemsByCategoryResponse": []}', headers: { 'Content-Type' => 'application/json' })
|
|
63
|
+
|
|
64
|
+
result = finding.find_items_by_category("9355")
|
|
65
|
+
expect(result).to eq("findItemsByCategoryResponse" => [])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "error handling" do
|
|
70
|
+
it "raises APIError on failure" do
|
|
71
|
+
stub_request(:get, finding_url)
|
|
72
|
+
.with(query: hash_including("OPERATION-NAME" => "findItemsByKeywords"))
|
|
73
|
+
.to_return(status: 500, body: "Server Error")
|
|
74
|
+
|
|
75
|
+
expect { finding.find_items_by_keywords("test") }.to raise_error(Ebay::APIError)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Ebay::Taxonomy do
|
|
4
|
+
let(:client) { Ebay.client }
|
|
5
|
+
let(:taxonomy) { described_class.new(client) }
|
|
6
|
+
|
|
7
|
+
before { stub_oauth_token }
|
|
8
|
+
|
|
9
|
+
describe "#get_default_category_tree_id" do
|
|
10
|
+
it "returns default category tree id" do
|
|
11
|
+
stub_request(:get, "https://api.ebay.com/commerce/taxonomy/v1/get_default_category_tree_id")
|
|
12
|
+
.with(query: { marketplace_id: "EBAY_US" })
|
|
13
|
+
.to_return(status: 200, body: '{"categoryTreeId": "0", "categoryTreeVersion": "119"}', headers: { 'Content-Type' => 'application/json' })
|
|
14
|
+
|
|
15
|
+
result = taxonomy.get_default_category_tree_id
|
|
16
|
+
expect(result).to eq("categoryTreeId" => "0", "categoryTreeVersion" => "119")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "accepts custom marketplace_id" do
|
|
20
|
+
stub_request(:get, "https://api.ebay.com/commerce/taxonomy/v1/get_default_category_tree_id")
|
|
21
|
+
.with(query: { marketplace_id: "EBAY_GB" })
|
|
22
|
+
.to_return(status: 200, body: '{"categoryTreeId": "3"}', headers: { 'Content-Type' => 'application/json' })
|
|
23
|
+
|
|
24
|
+
result = taxonomy.get_default_category_tree_id("EBAY_GB")
|
|
25
|
+
expect(result).to eq("categoryTreeId" => "3")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "#get_category_tree" do
|
|
30
|
+
it "returns category tree" do
|
|
31
|
+
stub_request(:get, "https://api.ebay.com/commerce/taxonomy/v1/category_tree/0")
|
|
32
|
+
.to_return(status: 200, body: '{"categoryTreeId": "0", "rootCategoryNode": {}}', headers: { 'Content-Type' => 'application/json' })
|
|
33
|
+
|
|
34
|
+
result = taxonomy.get_category_tree("0")
|
|
35
|
+
expect(result).to include("categoryTreeId" => "0")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "#get_category_subtree" do
|
|
40
|
+
it "returns category subtree" do
|
|
41
|
+
stub_request(:get, "https://api.ebay.com/commerce/taxonomy/v1/category_tree/0/get_category_subtree")
|
|
42
|
+
.with(query: { category_id: "9355" })
|
|
43
|
+
.to_return(status: 200, body: '{"categorySubtreeNode": {}}', headers: { 'Content-Type' => 'application/json' })
|
|
44
|
+
|
|
45
|
+
result = taxonomy.get_category_subtree("0", "9355")
|
|
46
|
+
expect(result).to eq("categorySubtreeNode" => {})
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/spec/examples.txt
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
example_id | status | run_time |
|
|
2
|
+
---------------------------------------- | ------ | --------------- |
|
|
3
|
+
./spec/ebay/browse_spec.rb[1:1:1] | passed | 0.00076 seconds |
|
|
4
|
+
./spec/ebay/browse_spec.rb[1:1:2] | passed | 0.00193 seconds |
|
|
5
|
+
./spec/ebay/browse_spec.rb[1:2:1] | passed | 0.00051 seconds |
|
|
6
|
+
./spec/ebay/browse_spec.rb[1:3:1] | passed | 0.00054 seconds |
|
|
7
|
+
./spec/ebay/browse_spec.rb[1:4:1] | passed | 0.00048 seconds |
|
|
8
|
+
./spec/ebay/client_spec.rb[1:1:1] | passed | 0.00004 seconds |
|
|
9
|
+
./spec/ebay/client_spec.rb[1:2:1] | passed | 0.00022 seconds |
|
|
10
|
+
./spec/ebay/client_spec.rb[1:2:2] | passed | 0.00031 seconds |
|
|
11
|
+
./spec/ebay/client_spec.rb[1:2:3] | passed | 0.00026 seconds |
|
|
12
|
+
./spec/ebay/client_spec.rb[1:3:1] | passed | 0.00041 seconds |
|
|
13
|
+
./spec/ebay/client_spec.rb[1:3:2] | passed | 0.00065 seconds |
|
|
14
|
+
./spec/ebay/client_spec.rb[1:3:3] | passed | 0.00039 seconds |
|
|
15
|
+
./spec/ebay/client_spec.rb[1:4:1] | passed | 0.00065 seconds |
|
|
16
|
+
./spec/ebay/client_spec.rb[1:5:1] | passed | 0.00044 seconds |
|
|
17
|
+
./spec/ebay/client_spec.rb[1:5:2] | passed | 0.00052 seconds |
|
|
18
|
+
./spec/ebay/client_spec.rb[1:5:3] | passed | 0.00059 seconds |
|
|
19
|
+
./spec/ebay/client_spec.rb[1:5:4] | passed | 0.00047 seconds |
|
|
20
|
+
./spec/ebay/client_spec.rb[1:6:1] | passed | 0.00057 seconds |
|
|
21
|
+
./spec/ebay/configuration_spec.rb[1:1:1] | passed | 0.00003 seconds |
|
|
22
|
+
./spec/ebay/configuration_spec.rb[1:1:2] | passed | 0.00004 seconds |
|
|
23
|
+
./spec/ebay/configuration_spec.rb[1:1:3] | passed | 0.00003 seconds |
|
|
24
|
+
./spec/ebay/configuration_spec.rb[1:1:4] | passed | 0.00003 seconds |
|
|
25
|
+
./spec/ebay/configuration_spec.rb[1:2:1] | passed | 0.00046 seconds |
|
|
26
|
+
./spec/ebay/configuration_spec.rb[1:2:2] | passed | 0.00005 seconds |
|
|
27
|
+
./spec/ebay/configuration_spec.rb[1:2:3] | passed | 0.00004 seconds |
|
|
28
|
+
./spec/ebay/configuration_spec.rb[1:3:1] | passed | 0.00004 seconds |
|
|
29
|
+
./spec/ebay/configuration_spec.rb[1:3:2] | passed | 0.00003 seconds |
|
|
30
|
+
./spec/ebay/configuration_spec.rb[1:3:3] | passed | 0.00005 seconds |
|
|
31
|
+
./spec/ebay/configuration_spec.rb[1:3:4] | passed | 0.00005 seconds |
|
|
32
|
+
./spec/ebay/configuration_spec.rb[1:4:1] | passed | 0.00003 seconds |
|
|
33
|
+
./spec/ebay/configuration_spec.rb[1:4:2] | passed | 0.00003 seconds |
|
|
34
|
+
./spec/ebay/finding_spec.rb[1:1:1] | passed | 0.00134 seconds |
|
|
35
|
+
./spec/ebay/finding_spec.rb[1:1:2] | passed | 0.02238 seconds |
|
|
36
|
+
./spec/ebay/finding_spec.rb[1:2:1] | passed | 0.00057 seconds |
|
|
37
|
+
./spec/ebay/finding_spec.rb[1:3:1] | passed | 0.00077 seconds |
|
|
38
|
+
./spec/ebay/finding_spec.rb[1:4:1] | passed | 0.00048 seconds |
|
|
39
|
+
./spec/ebay/finding_spec.rb[1:5:1] | passed | 0.00125 seconds |
|
|
40
|
+
./spec/ebay/taxonomy_spec.rb[1:1:1] | passed | 0.0007 seconds |
|
|
41
|
+
./spec/ebay/taxonomy_spec.rb[1:1:2] | passed | 0.00056 seconds |
|
|
42
|
+
./spec/ebay/taxonomy_spec.rb[1:2:1] | passed | 0.00212 seconds |
|
|
43
|
+
./spec/ebay/taxonomy_spec.rb[1:3:1] | passed | 0.0006 seconds |
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "ebay"
|
|
5
|
+
require "webmock/rspec"
|
|
6
|
+
|
|
7
|
+
RSpec.configure do |config|
|
|
8
|
+
config.expect_with :rspec do |expectations|
|
|
9
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
config.mock_with :rspec do |mocks|
|
|
13
|
+
mocks.verify_partial_doubles = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
17
|
+
config.filter_run_when_matching :focus
|
|
18
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
|
19
|
+
config.disable_monkey_patching!
|
|
20
|
+
config.warnings = true
|
|
21
|
+
|
|
22
|
+
if config.files_to_run.one?
|
|
23
|
+
config.default_formatter = "doc"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
config.order = :random
|
|
27
|
+
Kernel.srand config.seed
|
|
28
|
+
|
|
29
|
+
config.before(:each) do
|
|
30
|
+
WebMock.disable_net_connect!
|
|
31
|
+
Ebay.reset!
|
|
32
|
+
Ebay.configure do |c|
|
|
33
|
+
c.client_id = "test_client_id"
|
|
34
|
+
c.client_secret = "test_client_secret"
|
|
35
|
+
c.app_id = "test_app_id"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
config.after(:each) do
|
|
40
|
+
WebMock.reset!
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stub_oauth_token
|
|
45
|
+
stub_request(:post, "https://api.ebay.com/identity/v1/oauth2/token")
|
|
46
|
+
.to_return(
|
|
47
|
+
status: 200,
|
|
48
|
+
headers: { 'Content-Type' => 'application/json' },
|
|
49
|
+
body: {
|
|
50
|
+
access_token: "test_token",
|
|
51
|
+
expires_in: 7200,
|
|
52
|
+
token_type: "Application Access Token"
|
|
53
|
+
}.to_json
|
|
54
|
+
)
|
|
55
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ebay-api-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Eduardo Souza
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-26 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: httparty
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.21'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.21'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: json
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.6'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.6'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: bundler
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '2.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: dotenv
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '2.8'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.8'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rake
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '13.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '13.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rspec
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '3.12'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '3.12'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rubocop
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '1.50'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '1.50'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: webmock
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '3.18'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '3.18'
|
|
139
|
+
description: Ruby gem for integrating with eBay REST APIs. Supports Browse API, Finding
|
|
140
|
+
API, and Taxonomy API with OAuth 2.0 authentication.
|
|
141
|
+
email:
|
|
142
|
+
- eduardo@eduardosouza.com
|
|
143
|
+
executables: []
|
|
144
|
+
extensions: []
|
|
145
|
+
extra_rdoc_files: []
|
|
146
|
+
files:
|
|
147
|
+
- CHANGELOG.md
|
|
148
|
+
- LICENSE.txt
|
|
149
|
+
- README.md
|
|
150
|
+
- lib/ebay.rb
|
|
151
|
+
- lib/ebay/base.rb
|
|
152
|
+
- lib/ebay/browse.rb
|
|
153
|
+
- lib/ebay/client.rb
|
|
154
|
+
- lib/ebay/configuration.rb
|
|
155
|
+
- lib/ebay/errors.rb
|
|
156
|
+
- lib/ebay/finding.rb
|
|
157
|
+
- lib/ebay/taxonomy.rb
|
|
158
|
+
- lib/ebay/version.rb
|
|
159
|
+
- spec/ebay/browse_spec.rb
|
|
160
|
+
- spec/ebay/client_spec.rb
|
|
161
|
+
- spec/ebay/configuration_spec.rb
|
|
162
|
+
- spec/ebay/finding_spec.rb
|
|
163
|
+
- spec/ebay/taxonomy_spec.rb
|
|
164
|
+
- spec/examples.txt
|
|
165
|
+
- spec/spec_helper.rb
|
|
166
|
+
homepage: https://github.com/esouza/ebay-api-ruby
|
|
167
|
+
licenses:
|
|
168
|
+
- MIT
|
|
169
|
+
metadata:
|
|
170
|
+
homepage_uri: https://github.com/esouza/ebay-api-ruby
|
|
171
|
+
source_code_uri: https://github.com/esouza/ebay-api-ruby
|
|
172
|
+
changelog_uri: https://github.com/esouza/ebay-api-ruby/blob/main/CHANGELOG.md
|
|
173
|
+
post_install_message:
|
|
174
|
+
rdoc_options: []
|
|
175
|
+
require_paths:
|
|
176
|
+
- lib
|
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
178
|
+
requirements:
|
|
179
|
+
- - ">="
|
|
180
|
+
- !ruby/object:Gem::Version
|
|
181
|
+
version: 2.7.0
|
|
182
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
|
+
requirements:
|
|
184
|
+
- - ">="
|
|
185
|
+
- !ruby/object:Gem::Version
|
|
186
|
+
version: '0'
|
|
187
|
+
requirements: []
|
|
188
|
+
rubygems_version: 3.5.21
|
|
189
|
+
signing_key:
|
|
190
|
+
specification_version: 4
|
|
191
|
+
summary: Ruby gem for the eBay REST APIs
|
|
192
|
+
test_files: []
|