mercado-libre 0.0.2
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/.gems +3 -0
- data/.gitignore +2 -0
- data/Makefile +6 -0
- data/env.sh.example +3 -0
- data/lib/mercado-libre.rb +3 -0
- data/lib/mercado_libre/api.rb +94 -0
- data/lib/mercado_libre/item.rb +33 -0
- data/lib/mercado_libre/version.rb +3 -0
- data/mercado-libre.gemspec +16 -0
- data/test/item.rb +63 -0
- data/test/mercadolibre.rb +105 -0
- data/test/mercadolibre_mock_server.rb +62 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 363f6161a791d57042f5398e81818cd213f77f1b
|
4
|
+
data.tar.gz: 61ba177f45aac40b736396aaedd46da57460b748
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 92f080efeed17ad6e8e6f0aa0a21d1885bb43a77e635e38f3c08195cdbb8c3ac2dbf0d30d68db35e108a047260cc72bbfc9a826fd8d5139ce7c45d9d71f04579
|
7
|
+
data.tar.gz: 734b70b76553754d2cb56bc78d4cd37b9570b042c293715b7143950453d33aa439f9f47c0b14d73069fc781856e38ad4899361f2f57b92df03e971ee55712e84
|
data/.gems
ADDED
data/.gitignore
ADDED
data/Makefile
ADDED
data/env.sh.example
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module MercadoLibre
|
2
|
+
require 'requests'
|
3
|
+
require "requests/sugar"
|
4
|
+
class API
|
5
|
+
attr_accessor :access_token
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
@app_key = params.fetch(:app_key)
|
9
|
+
@app_secret = params.fetch(:app_secret)
|
10
|
+
@host = params.fetch(:host) { ENV.fetch('MERCADOLIBRE_API_HOST') }
|
11
|
+
end
|
12
|
+
|
13
|
+
def publish_item(item)
|
14
|
+
url = "/items"
|
15
|
+
response = authenticated_request(:post, url, item.publishable_hash.to_json)
|
16
|
+
|
17
|
+
Item.new(response)
|
18
|
+
end
|
19
|
+
|
20
|
+
def update_item(item_id, params)
|
21
|
+
url = "/items/#{item_id}"
|
22
|
+
response = authenticated_request(:put, url, params.to_json)
|
23
|
+
|
24
|
+
Item.new(response)
|
25
|
+
end
|
26
|
+
|
27
|
+
def close_item(item_id)
|
28
|
+
url = "/items/#{item_id}"
|
29
|
+
authenticated_request(:put, url, {status: :closed}.to_json)
|
30
|
+
end
|
31
|
+
|
32
|
+
def relist_item(item_id, params)
|
33
|
+
url = "/items/#{item_id}/relist"
|
34
|
+
payload = {
|
35
|
+
price: params.fetch(:price),
|
36
|
+
quantity: params.fetch(:quantity),
|
37
|
+
listing_type_id: params.fetch(:listing_type_id)
|
38
|
+
}
|
39
|
+
response = authenticated_request(:post, url, payload.to_json)
|
40
|
+
|
41
|
+
Item.new response
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_item(item_id)
|
45
|
+
url = "/items/#{item_id}"
|
46
|
+
response = authenticated_request(:get, url)
|
47
|
+
|
48
|
+
Item.new(response)
|
49
|
+
end
|
50
|
+
|
51
|
+
def authenticated_request(verb, url, data = {})
|
52
|
+
url_with_token = "#{url}?access_token=#{access_token}"
|
53
|
+
begin
|
54
|
+
request(verb, url_with_token, data)
|
55
|
+
rescue Requests::Error
|
56
|
+
@retries ||=0
|
57
|
+
@retries +=1
|
58
|
+
if $!.message.strip == 'Forbidden'
|
59
|
+
@access_token = nil
|
60
|
+
authenticated_request(verb, url, data) if @retries < 10
|
61
|
+
else
|
62
|
+
raise $!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def request(verb, url, data)
|
68
|
+
url = "#{@host}#{url}"
|
69
|
+
verb = verb.to_s.upcase
|
70
|
+
begin
|
71
|
+
response = Requests.request(verb, url, data: data)
|
72
|
+
rescue StandardError => e
|
73
|
+
new_message = [e.message.strip,e.response.body].join("\n")
|
74
|
+
raise $!, new_message, $!.backtrace
|
75
|
+
end
|
76
|
+
JSON.parse(response.body)
|
77
|
+
end
|
78
|
+
|
79
|
+
def access_token
|
80
|
+
@access_token ||= request_access_token
|
81
|
+
end
|
82
|
+
|
83
|
+
def request_access_token
|
84
|
+
url = "/oauth/token"
|
85
|
+
data = {
|
86
|
+
client_id: @app_key,
|
87
|
+
client_secret: @app_secret,
|
88
|
+
grant_type: 'client_credentials'
|
89
|
+
}
|
90
|
+
|
91
|
+
request(:post, url, data).fetch('access_token')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MercadoLibre
|
2
|
+
class Item
|
3
|
+
def self.publishable_params
|
4
|
+
[:site_id, :title, :category_id, :price, :currency_id, :available_quantity,
|
5
|
+
:buying_mode, :listing_type_id, :condition, :description, :video_id, :warranty,
|
6
|
+
:pictures, :shipping]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.attr_list
|
10
|
+
other_fields = [:id, :start_time, :stop_time, :end_time, :permalink, :thumbnail,
|
11
|
+
:secure_thumbnail, :descriptions, :accepts_mercadopago, :status, :date_created,
|
12
|
+
:last_updated, :non_mercado_pago_payment_methods ]
|
13
|
+
|
14
|
+
self.publishable_params + other_fields
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor *attr_list
|
18
|
+
|
19
|
+
def initialize(params = {})
|
20
|
+
params = Hash[params.map { |k, v| [k.to_sym, v] }] #Symbolize keys
|
21
|
+
self.class.attr_list.each do |k|
|
22
|
+
send("#{k}=", params[k]) if params[k]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def publishable_hash
|
27
|
+
hash = {}
|
28
|
+
self.class.publishable_params.each { |k| hash[k] = send(k) }
|
29
|
+
|
30
|
+
hash
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "mercado-libre"
|
3
|
+
s.version = "0.0.2"
|
4
|
+
s.summary = "MercadoLibre API"
|
5
|
+
s.description = "mercado-libre"
|
6
|
+
s.authors = ["CarlosIPe"]
|
7
|
+
s.email = ["carlos2@compendium.com.ar"]
|
8
|
+
s.homepage = "https://github.com/carlosipe/mercado-libre"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
|
13
|
+
s.add_dependency "requests", '~> 1'
|
14
|
+
s.add_development_dependency "cutest", '~> 1'
|
15
|
+
s.add_development_dependency "mock-server", '~> 0'
|
16
|
+
end
|
data/test/item.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require 'mercado-libre'
|
3
|
+
include MercadoLibre
|
4
|
+
|
5
|
+
def assignable_properties
|
6
|
+
{
|
7
|
+
site_id: 'MLA',
|
8
|
+
title: 'title',
|
9
|
+
category_id: 'MLA1212',
|
10
|
+
price: 100.19,
|
11
|
+
currency_id: 'ARS',
|
12
|
+
available_quantity: 10,
|
13
|
+
buying_mode: 'buy_it_now',
|
14
|
+
listing_type_id: 'gold_special',
|
15
|
+
condition: 'new',
|
16
|
+
description: 'description eheh',
|
17
|
+
warranty: 'GARANTIA TOTAL CONTRA FALLAS',
|
18
|
+
video_id: nil,
|
19
|
+
pictures: [],
|
20
|
+
shipping: {mode: 'me2', local_pick_up: true},
|
21
|
+
start_time: "2015-12-30T20:28:49.097Z",
|
22
|
+
stop_time: "2016-02-28T20:28:49.097Z",
|
23
|
+
end_time: "2016-02-28T20:28:49.097Z",
|
24
|
+
permalink: "http://articulo.mercadolibre.com.ar/MLA-597837524-title-_JM",
|
25
|
+
thumbnail: "",
|
26
|
+
secure_thumbnail: "",
|
27
|
+
descriptions:[{id: "MLA597837524-998844341"}],
|
28
|
+
accepts_mercadopago: true,
|
29
|
+
non_mercado_pago_payment_methods:[],
|
30
|
+
status: "not_yet_active",
|
31
|
+
date_created: "2015-12-30T20:28:49.292Z",
|
32
|
+
last_updated: "2015-12-30T20:28:49.292Z"
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def publishable_fields
|
37
|
+
{
|
38
|
+
site_id: 'MLA',
|
39
|
+
title: 'title',
|
40
|
+
category_id: 'MLA1212',
|
41
|
+
price: 100.19,
|
42
|
+
currency_id: 'ARS',
|
43
|
+
available_quantity: 10,
|
44
|
+
buying_mode: 'buy_it_now',
|
45
|
+
listing_type_id: 'gold_special',
|
46
|
+
condition: 'new',
|
47
|
+
description: 'description eheh',
|
48
|
+
warranty: 'GARANTIA TOTAL CONTRA FALLAS',
|
49
|
+
video_id: nil,
|
50
|
+
pictures: [],
|
51
|
+
shipping: {mode: 'me2', local_pick_up: true}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
test "item sets all assignable properties" do
|
56
|
+
item = Item.new(assignable_properties)
|
57
|
+
assignable_properties.each{|k,v| assert_equal(item.send(k),v)}
|
58
|
+
end
|
59
|
+
|
60
|
+
test "item publishable_hash" do
|
61
|
+
item = Item.new(publishable_fields)
|
62
|
+
assert_equal(item.publishable_hash, publishable_fields)
|
63
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
require 'mercado-libre'
|
3
|
+
require './test/mercadolibre_mock_server'
|
4
|
+
include MercadoLibre
|
5
|
+
mercadolibre_mock = MercadoLibreMock.new
|
6
|
+
mercadolibre_mock.run
|
7
|
+
|
8
|
+
File.read("env.sh").scan(/(.*?)="?(.*)"?$/).each do |key, value|
|
9
|
+
ENV[key] ||= value
|
10
|
+
end
|
11
|
+
|
12
|
+
def api
|
13
|
+
API.new(
|
14
|
+
app_key: ENV['ML_TEST_APP_KEY'],
|
15
|
+
app_secret: ENV['ML_TEST_APP_SECRET'],
|
16
|
+
host: ENV['TESTING_AGAINST_ML_SERVER'] ? 'https://api.mercadolibre.com' : 'http://localhost:4000'
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def item
|
21
|
+
Item.new(
|
22
|
+
site_id: 'MLA',
|
23
|
+
title: 'title',
|
24
|
+
category_id: 'MLA1227',
|
25
|
+
price: 100.19,
|
26
|
+
currency_id: 'ARS',
|
27
|
+
available_quantity: 10,
|
28
|
+
buying_mode: 'buy_it_now',
|
29
|
+
listing_type_id: 'bronze',
|
30
|
+
condition: 'new',
|
31
|
+
description: 'description eheh',
|
32
|
+
video_id: nil,
|
33
|
+
warranty: 'GARANTIA TOTAL CONTRA FALLAS',
|
34
|
+
pictures: [],
|
35
|
+
shipping: {"mode"=> 'me2', "local_pick_up" => true}
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
test "API gets access token" do
|
40
|
+
assert( api.request_access_token() =~ /^APP/ )
|
41
|
+
end
|
42
|
+
|
43
|
+
test "API.publish item returns item with id and title" do
|
44
|
+
published_item = api.publish_item(item)
|
45
|
+
assert(published_item.id =~ /^MLA(\d*)/ )
|
46
|
+
assert_equal(published_item.title, 'Title')
|
47
|
+
end
|
48
|
+
|
49
|
+
test "publish item and check its published" do
|
50
|
+
published_item = api.publish_item(item)
|
51
|
+
assert_equal(api.get_item(published_item.id).title, 'Title')
|
52
|
+
end
|
53
|
+
|
54
|
+
test "updates item and check its updated" do
|
55
|
+
published_item = api.publish_item(item)
|
56
|
+
params = {title: 'New title', price: 200, available_quantity: 99}
|
57
|
+
api.update_item(published_item.id, params)
|
58
|
+
retrieved_item = api.get_item(published_item.id)
|
59
|
+
|
60
|
+
assert_equal(retrieved_item.title, 'New Title')
|
61
|
+
assert_equal(retrieved_item.price, 200)
|
62
|
+
assert_equal(retrieved_item.available_quantity, 99)
|
63
|
+
assert_equal(retrieved_item.id, published_item.id)
|
64
|
+
end
|
65
|
+
|
66
|
+
test "publish item returns item with permalink" do
|
67
|
+
published_item = api.publish_item(item)
|
68
|
+
assert(published_item.permalink =~ /http/)
|
69
|
+
end
|
70
|
+
|
71
|
+
test "closes item and status is closed" do
|
72
|
+
published_item = api.publish_item(item)
|
73
|
+
sleep(10) if ENV['TESTING_AGAINST_ML_SERVER']
|
74
|
+
api.close_item(published_item.id)
|
75
|
+
assert_equal(api.get_item(published_item.id).status, 'closed')
|
76
|
+
end
|
77
|
+
|
78
|
+
test "relists an item returns item with new ML_ID and values" do
|
79
|
+
published_item = api.publish_item(item)
|
80
|
+
sleep(10) if ENV['TESTING_AGAINST_ML_SERVER']
|
81
|
+
api.close_item(published_item.id)
|
82
|
+
relisted_item = api.relist_item(published_item.id,{
|
83
|
+
price: 90,
|
84
|
+
quantity: 40,
|
85
|
+
listing_type_id: 'bronze'
|
86
|
+
})
|
87
|
+
assert_equal(relisted_item.price, 90)
|
88
|
+
assert_equal(relisted_item.available_quantity, 40)
|
89
|
+
end
|
90
|
+
|
91
|
+
test "returns verbose exceptions" do
|
92
|
+
begin
|
93
|
+
api.request(:post, '/oauth/token', {client_id: '123'})
|
94
|
+
rescue StandardError =>e
|
95
|
+
verbose_error_message = "Bad Request\n{\"message\":\"grant_type is a required parameter\",\"error\":\"invalid_request\",\"status\":400,\"cause\":[]}"
|
96
|
+
assert_equal(e.message, verbose_error_message )
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
test "authenticated_request gets new access token when forbidden" do
|
101
|
+
ml = api
|
102
|
+
ml.access_token = 'Invalid one'
|
103
|
+
published_item = ml.publish_item(item)
|
104
|
+
assert(ml.access_token =~ /^APP/)
|
105
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'mock_server'
|
2
|
+
class MercadoLibreMock
|
3
|
+
include MockServer::Methods
|
4
|
+
PRODUCTS = {}
|
5
|
+
|
6
|
+
def run
|
7
|
+
mock_server {
|
8
|
+
post "/oauth/token" do
|
9
|
+
if !params['grant_type']
|
10
|
+
status 400
|
11
|
+
content_type :json
|
12
|
+
'{"message":"grant_type is a required parameter","error":"invalid_request","status":400,"cause":[]}'
|
13
|
+
else
|
14
|
+
content_type :json
|
15
|
+
{ access_token: 'APP12343' }.to_json
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
post '/items' do
|
20
|
+
halt 403 unless params[:access_token] == 'APP12343'
|
21
|
+
data = JSON.parse(request.body.read)
|
22
|
+
data['id'] = 'MLA1234'
|
23
|
+
data['title'] = data['title'].split.map(&:capitalize)*' '
|
24
|
+
data['permalink'] = 'http://mercadolibre.com/MLA/item/MLA1234'
|
25
|
+
PRODUCTS["MLA1234"] = data
|
26
|
+
|
27
|
+
content_type :json
|
28
|
+
PRODUCTS.fetch('MLA1234').to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
get '/items/:id' do |id|
|
32
|
+
content_type :json
|
33
|
+
PRODUCTS.fetch(id).to_json { raise Sinatra::NotFound }
|
34
|
+
end
|
35
|
+
|
36
|
+
put '/items/:id' do |id|
|
37
|
+
halt 403 unless params[:access_token] == 'APP12343'
|
38
|
+
product = PRODUCTS.fetch(id) { raise Sinatra::NotFound }
|
39
|
+
data = JSON.parse(request.body.read)
|
40
|
+
PRODUCTS[id] = product.merge(data)
|
41
|
+
PRODUCTS[id]['title'] = PRODUCTS[id]['title'].split.map(&:capitalize)*' '
|
42
|
+
|
43
|
+
content_type :json
|
44
|
+
PRODUCTS[id].to_json
|
45
|
+
end
|
46
|
+
|
47
|
+
post '/items/:id/relist' do |id|
|
48
|
+
halt 403 unless params[:access_token] == 'APP12343'
|
49
|
+
product = PRODUCTS.fetch(id) { raise Sinatra::NotFound }
|
50
|
+
data = JSON.parse(request.body.read)
|
51
|
+
product['id'] = 'MLA4321'
|
52
|
+
product['price'] = data.fetch('price')
|
53
|
+
product['available_quantity'] = data.fetch('quantity')
|
54
|
+
product['listing_type_id'] = data.fetch 'listing_type_id'
|
55
|
+
PRODUCTS['MLA4321'] = product
|
56
|
+
|
57
|
+
content_type :json
|
58
|
+
PRODUCTS['MLA4321'].to_json
|
59
|
+
end
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mercado-libre
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- CarlosIPe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: requests
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: cutest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mock-server
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: mercado-libre
|
56
|
+
email:
|
57
|
+
- carlos2@compendium.com.ar
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gems"
|
63
|
+
- ".gitignore"
|
64
|
+
- Makefile
|
65
|
+
- env.sh.example
|
66
|
+
- lib/mercado-libre.rb
|
67
|
+
- lib/mercado_libre/api.rb
|
68
|
+
- lib/mercado_libre/item.rb
|
69
|
+
- lib/mercado_libre/version.rb
|
70
|
+
- mercado-libre.gemspec
|
71
|
+
- test/item.rb
|
72
|
+
- test/mercadolibre.rb
|
73
|
+
- test/mercadolibre_mock_server.rb
|
74
|
+
homepage: https://github.com/carlosipe/mercado-libre
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.4.5.1
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: MercadoLibre API
|
98
|
+
test_files: []
|