diff --git a/.gitignore b/.gitignore index b3f16c1..6a439c8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .settings/ target/ mockapi/node_modules +/.idea/ +/sdk.iml diff --git a/mockapi/app.js b/mockapi/app.js index b39715b..fcf197a 100644 --- a/mockapi/app.js +++ b/mockapi/app.js @@ -3,69 +3,91 @@ var fs = require('fs'); var app = express.createServer(); -app.configure(function(){ +app.configure(function () { app.use(express.methodOverride()); app.use(express.bodyParser()); }); -app.post('/oauth/token', function(req, res) { - if(req.query["grant_type"]=="authorization_code") { - if(req.query["code"]=="bad code") { - res.send({"message":"Error validando el parámetro code","error":"invalid_grant","status":400,"cause":[]}, 400); - } else if(req.query["code"]=="valid code without refresh token") { +app.post('/oauth/token', function (req, res) { + if (req.query["grant_type"] == "authorization_code") { + if (req.query["code"] == "bad code") { res.send({ - "access_token" : "valid token", - "token_type" : "bearer", - "expires_in" : 10800, - "scope" : "write read" + "message": "Error validando el parámetro code", + "error": "invalid_grant", + "status": 400, + "cause": [] + }, 400); + } else if (req.query["code"] == "valid code without refresh token") { + res.send({ + "access_token": "valid token", + "token_type": "bearer", + "expires_in": 10800, + "scope": "write read" }); - } else if(req.query["code"]=="valid code with refresh token") { + } else if (req.query["code"] == "valid code with refresh token") { res.send({ - "access_token" : "valid token", - "token_type" : "bearer", - "expires_in" : 10800, - "refresh_token" : "valid refresh token", - "scope" : "write read" + "access_token": "valid token", + "token_type": "bearer", + "expires_in": 10800, + "refresh_token": "valid refresh token", + "scope": "write read" }); } else { res.send(404); } - } else if(req.query['grant_type']=='refresh_token') { - if(req.query['refresh_token']=='valid refresh token') { + } else if (req.query['grant_type'] == 'refresh_token') { + if (req.query['refresh_token'] == 'valid refresh token') { res.send({ - "access_token" : "valid token", - "token_type" : "bearer", - "expires_in" : 10800, - "scope" : "write read" + "access_token": "valid token", + "token_type": "bearer", + "expires_in": 10800, + "scope": "write read" }); } } }); -app.get('/sites', function(req, res) { - res.send([{"id":"MLA","name":"Argentina"},{"id":"MLB","name":"Brasil"},{"id":"MCO","name":"Colombia"},{"id":"MCR","name":"Costa Rica"},{"id":"MEC","name":"Ecuador"},{"id":"MLC","name":"Chile"},{"id":"MLM","name":"Mexico"},{"id":"MLU","name":"Uruguay"},{"id":"MLV","name":"Venezuela"},{"id":"MPA","name":"Panamá"},{"id":"MPE","name":"Perú"},{"id":"MPT","name":"Portugal"},{"id":"MRD","name":"Dominicana"}]); +app.get('/sites', function (req, res) { + res.send([{"id": "MLA", "name": "Argentina"}, {"id": "MLB", "name": "Brasil"}, { + "id": "MCO", + "name": "Colombia" + }, {"id": "MCR", "name": "Costa Rica"}, {"id": "MEC", "name": "Ecuador"}, { + "id": "MLC", + "name": "Chile" + }, {"id": "MLM", "name": "Mexico"}, {"id": "MLU", "name": "Uruguay"}, { + "id": "MLV", + "name": "Venezuela" + }, {"id": "MPA", "name": "Panamá"}, {"id": "MPE", "name": "Perú"}, {"id": "MPT", "name": "Portugal"}, { + "id": "MRD", + "name": "Dominicana" + }]); }); -app.get('/users/me', function(req, res) { - if(req.query['access_token']=='valid token') { - res.send({"id":123456,"nickname":"foobar"}); - } else if(req.query['access_token']=='expired token') { +app.get('/users/me', function (req, res) { + if (req.query['access_token'] == 'valid token') { + res.send({"id": 123456, "nickname": "foobar"}); + } else if (req.query['access_token'] == 'expired token') { res.send(404); } else { - res.send({"message":"The User ID must match the consultant's","error":"forbidden","status":403,"cause":[]}, 403); + res.send({ + "message": "The User ID must match the consultant's", + "error": "forbidden", + "status": 403, + "cause": [] + }, 403); } }); -app.post('/items', function(req, res) { - if(req.query['access_token']=='valid token') { - if(req.body && req.body.foo == "bar") { +app.post('/items', function (req, res) { + if (req.query['access_token'] == 'valid token') { + if (req.body && req.body.foo == "bar") { res.send(201); } else { res.send(400); } - } else if(req.query['access_token']=='expired token') { + } else if (req.query['access_token'] == 'expired token') { res.send(404); } else { res.send(403); @@ -73,31 +95,31 @@ app.post('/items', function(req, res) { }); -app.put('/items/123', function(req, res) { - if(req.query['access_token']=='valid token') { - if(req.body && req.body.foo == "bar") { +app.put('/items/123', function (req, res) { + if (req.query['access_token'] == 'valid token') { + if (req.body && req.body.foo == "bar") { res.send(200); } else { res.send(400); } - } else if(req.query['access_token']=='expired token') { + } else if (req.query['access_token'] == 'expired token') { res.send(404); } else { res.send(403); } }); -app.delete('/items/123', function(req, res) { - if(req.query['access_token']=='valid token') { +app.delete('/items/123', function (req, res) { + if (req.query['access_token'] == 'valid token') { res.send(200); - } else if(req.query['access_token']=='expired token') { + } else if (req.query['access_token'] == 'expired token') { res.send(404); } else { res.send(403); } }); -app.get('/echo/user_agent',function(req,res) { +app.get('/echo/user_agent', function (req, res) { if (req.headers['user-agent'].match(/MELI-JAVA-SDK-.*/)) res.send(200); else diff --git a/mockapi/package.json b/mockapi/package.json index e3497bb..3788de5 100644 --- a/mockapi/package.json +++ b/mockapi/package.json @@ -1,8 +1,8 @@ { - "name": "mockapi", - "version": "1.0.0", - "dependencies": { - "express" : "2.5.x" - }, - "engine": "node ~> 0.8.x" + "name": "mockapi", + "version": "1.0.0", + "dependencies": { + "express": "2.5.x" + }, + "engine": "node ~> 0.8.x" } diff --git a/pom.xml b/pom.xml index d6261c7..17b6ea1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,58 +1,107 @@ - 4.0.0 - com.mercadolibre - sdk - 0.0.3-SNAPSHOT - - - repo - https://github.com/mercadolibre/java-sdk-repo/raw/master/releases - - - snapshot-repo - https://github.com/mercadolibre/java-sdk-repo/raw/master/snapshots - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.0.2 - - 1.6 - 1.6 - - + com.mercadolibre + sdk + 0.0.4-SNAPSHOT - - org.apache.maven.plugins - maven-surefire-plugin - - true - - + + + com.ning + async-http-client + 1.7.4 + + + com.google.code.gson + gson + 2.8.2 + + + junit + junit + 4.12 + test + + + org.mockito + mockito-all + 1.10.19 + test + + + commons-io + commons-io + 2.6 + test + + - - + + + repo + https://github.com/mercadolibre/java-sdk-repo/raw/master/releases + + + snapshot-repo + https://github.com/mercadolibre/java-sdk-repo/raw/master/snapshots + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-site-plugin + 3.6 + + + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.9 + + false + + + + + index + summary + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.5 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.7 + + + html + xml + + + + + - - - junit - junit - 4.10 - - - com.ning - async-http-client - 1.7.4 - - - com.google.code.gson - gson - 2.2.2 - - diff --git a/src/main/java/com/mercadolibre/sdk/Meli.java b/src/main/java/com/mercadolibre/sdk/Meli.java index 4a39897..5b18bcc 100644 --- a/src/main/java/com/mercadolibre/sdk/Meli.java +++ b/src/main/java/com/mercadolibre/sdk/Meli.java @@ -1,331 +1,242 @@ package com.mercadolibre.sdk; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.concurrent.ExecutionException; - import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; +import com.mercadolibre.sdk.dao.MeliHttpDao; +import com.mercadolibre.sdk.dao.impl.MeliHttpDaoImpl; +import com.mercadolibre.sdk.exception.AuthorizationFailure; +import com.mercadolibre.sdk.exception.MeliException; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.AsyncHttpClientConfig; -import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder; import com.ning.http.client.FluentStringsMap; import com.ning.http.client.Response; -public class Meli { - - public static String apiUrl = "https://api.mercadolibre.com"; - - /** - * Availables auth sites. One user - application can only operate in one site - * - */ - - public static enum AuthUrls { - MLA("https://auth.mercadolibre.com.ar"), // Argentina - MLB("https://auth.mercadolivre.com.br"), // Brasil - MCO("https://auth.mercadolibre.com.co"), // Colombia - MCR("https://auth.mercadolibre.com.cr"), // Costa Rica - MEC("https://auth.mercadolibre.com.ec"), // Ecuador - MLC("https://auth.mercadolibre.cl"), // Chile - MLM("https://auth.mercadolibre.com.mx"), // Mexico - MLU("https://auth.mercadolibre.com.uy"), // Uruguay - MLV("https://auth.mercadolibre.com.ve"), // Venezuela - MPA("https://auth.mercadolibre.com.pa"), // Panama - MPE("https://auth.mercadolibre.com.pe"), // Peru - MPT("https://auth.mercadolibre.com.pt"), // Portugal - MRD("https://auth.mercadolibre.com.do"); // Dominicana - - private String value; - - - private AuthUrls(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - }; - - private String accessToken; - private String refreshToken; - private Long clientId; - private String clientSecret; - private AsyncHttpClient http; - /** news **/ - private Long expiresIn; - private String scope; - private String userId; - private String tokenType; - - - - { - AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder() - .setUserAgent("MELI-JAVA-SDK-0.0.4").build(); - http = new AsyncHttpClient(cf); - } - - public Meli(Long clientId, String clientSecret) { - this.clientId = clientId; - this.clientSecret = clientSecret; - } - - public Meli(Long clientId, String clientSecret, String accessToken) { - this.accessToken = accessToken; - this.clientId = clientId; - this.clientSecret = clientSecret; - } - - public Meli(Long clientId, String clientSecret, String accessToken, String refreshToken) { - this.accessToken = accessToken; - this.clientId = clientId; - this.clientSecret = clientSecret; - this.refreshToken = refreshToken; - } - - public String getAccessToken() { - return this.accessToken; - } - - public String getRefreshToken() { - return this.refreshToken; - } - /** news **/ - public Long getExpiresIn() { - return this.expiresIn; - } - - public String getScope() { - return this.scope; - } - - public String getUserId() { - return this.userId; - } +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; - public String getTokenType() { - return this.tokenType; - } +public class Meli { - public Response get(String path) throws MeliException { - return get(path, new FluentStringsMap()); - } + /** + * Availables auth sites. One user - application can only operate in one site + */ + + public enum AuthUrls { + MLA("https://auth.mercadolibre.com.ar"), // Argentina + MLB("https://auth.mercadolivre.com.br"), // Brasil + MCO("https://auth.mercadolibre.com.co"), // Colombia + MCR("https://auth.mercadolibre.com.cr"), // Costa Rica + MEC("https://auth.mercadolibre.com.ec"), // Ecuador + MLC("https://auth.mercadolibre.cl"), // Chile + MLM("https://auth.mercadolibre.com.mx"), // Mexico + MLU("https://auth.mercadolibre.com.uy"), // Uruguay + MLV("https://auth.mercadolibre.com.ve"), // Venezuela + MPA("https://auth.mercadolibre.com.pa"), // Panama + MPE("https://auth.mercadolibre.com.pe"), // Peru + MPT("https://auth.mercadolibre.com.pt"), // Portugal + MRD("https://auth.mercadolibre.com.do"); // Dominicana + + private String value; + + + AuthUrls(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + private String accessToken; + private String refreshToken; + private Long clientId; + private String clientSecret; + /** + * news + **/ + private Long expiresIn; + private String scope; + private String userId; + private String tokenType; + private MeliHttpDao meliHttpDao; + + + { + AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder() + .setUserAgent("MELI-JAVA-SDK-0.0.4").build(); + meliHttpDao = new MeliHttpDaoImpl(new AsyncHttpClient(cf)); + } + + public Meli(Long clientId, String clientSecret) { + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + public Meli(Long clientId, String clientSecret, String accessToken) { + this.accessToken = accessToken; + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + public Meli(Long clientId, String clientSecret, String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.refreshToken = refreshToken; + } + + public String getAccessToken() { + return this.accessToken; + } + + public String getRefreshToken() { + return this.refreshToken; + } + + /** + * news + **/ + public Long getExpiresIn() { + return this.expiresIn; + } + + public String getScope() { + return this.scope; + } + + public String getUserId() { + return this.userId; + } + + public String getTokenType() { + return this.tokenType; + } + + public void setMeliHttpDao(MeliHttpDao meliHttpDao) { + this.meliHttpDao = meliHttpDao; + } + + public void refreshAccessToken() throws AuthorizationFailure { + FluentStringsMap params = new FluentStringsMap(); + params.add("grant_type", "refresh_token"); + params.add("client_id", String.valueOf(this.clientId)); + params.add("client_secret", this.clientSecret); + params.add("refresh_token", this.refreshToken); + try { + Response response = meliHttpDao.post("/oauth/token", params); + parseToken(response); + } catch (AuthorizationFailure e1) { + System.out.println(e1.getMessage()); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + + } + + /** + * @param callback: The callback URL. Must be the applications redirect URI + * @param authUrl: The authorization URL. Get from Meli.AuthUrls + * @return the authorization URL + */ + public String getAuthUrl(String callback, AuthUrls authUrl) { + try { + return authUrl.getValue() + "/authorization?response_type=code&client_id=" + + clientId + + "&redirect_uri=" + + URLEncoder.encode(callback, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return authUrl + "/authorization?response_type=code&client_id=" + + clientId + "&redirect_uri=" + callback; + } + } + + public void authorize(String code, String redirectUri) throws AuthorizationFailure, MeliException { + FluentStringsMap params = new FluentStringsMap(); + + params.add("grant_type", "authorization_code"); + params.add("client_id", String.valueOf(clientId)); + params.add("client_secret", clientSecret); + params.add("code", code); + params.add("redirect_uri", redirectUri); + + Response response = post("/oauth/token", params); + + parseToken(response); + } + + public Response get(String path) throws MeliException { + return meliHttpDao.get(path); + } + + public Response get(String path, FluentStringsMap params) throws MeliException { + return meliHttpDao.get(path, params); + } + + public Response post(String path, FluentStringsMap params) throws MeliException { + return meliHttpDao.post(path, params); + } + + public Response post(String path, FluentStringsMap params, String body) throws MeliException { + return meliHttpDao.post(path, params, body); + } + + public Response put(String path, FluentStringsMap params, String body) throws MeliException { + return meliHttpDao.put(path, params, body); + } + + public Response delete(String path, FluentStringsMap params) throws MeliException { + return meliHttpDao.delete(path, params); + } + + private void parseToken(Response response) throws AuthorizationFailure { + String responseBody; + try { + responseBody = response.getResponseBody(); + } catch (IOException e) { + throw new AuthorizationFailure(e); + } + + JsonParser p = new JsonParser(); + JsonObject object; - private BoundRequestBuilder prepareGet(String path, FluentStringsMap params) { - return http.prepareGet(apiUrl + path) - .addHeader("Accept", "application/json") - .setQueryParameters(params); + try { + object = p.parse(responseBody).getAsJsonObject(); + } catch (JsonSyntaxException e) { + throw new AuthorizationFailure(responseBody); } + if (response.getStatusCode() == 200) { - private BoundRequestBuilder prepareDelete(String path, - FluentStringsMap params) { - return http.prepareDelete(apiUrl + path) - .addHeader("Accept", "application/json") - .setQueryParameters(params); - } - - private BoundRequestBuilder preparePost(String path, - FluentStringsMap params, String body) { - return http.preparePost(apiUrl + path) - .addHeader("Accept", "application/json") - .setQueryParameters(params) - .setHeader("Content-Type", "application/json").setBody(body) - .setBodyEncoding("UTF-8"); - } - - private BoundRequestBuilder preparePut(String path, - FluentStringsMap params, String body) { - return http.preparePut(apiUrl + path) - .addHeader("Accept", "application/json") - .setQueryParameters(params) - .setHeader("Content-Type", "application/json").setBody(body) - .setBodyEncoding("UTF-8"); - } - - private BoundRequestBuilder preparePost(String path, FluentStringsMap params) { - return http.preparePost(apiUrl + path) - .addHeader("Accept", "application/json") - .setQueryParameters(params); - } - - - - public Response get(String path, FluentStringsMap params) throws MeliException { - - BoundRequestBuilder r = prepareGet(path, params); - - Response response; - try { - response = r.execute().get(); - } catch (Exception e) { - throw new MeliException(e); - } - - - - return response; - } - - public void refreshAccessToken() throws AuthorizationFailure { - FluentStringsMap params = new FluentStringsMap(); - params.add("grant_type", "refresh_token"); - params.add("client_id", String.valueOf(this.clientId)); - params.add("client_secret", this.clientSecret); - params.add("refresh_token", this.refreshToken); - try { - BoundRequestBuilder req = preparePost("/oauth/token", params); - parseToken(req); - } catch (AuthorizationFailure e1) { - System.out.println(e1.getMessage()); - }catch (Exception e){ - System.out.println(e.getMessage()); - } - - } - - /** - * - * @param callback: The callback URL. Must be the applications redirect URI - * @param authUrl: The authorization URL. Get from Meli.AuthUrls - * @return the authorization URL - */ - public String getAuthUrl(String callback, AuthUrls authUrl) { - try { - return authUrl.getValue() + "/authorization?response_type=code&client_id=" - + clientId - + "&redirect_uri=" - + URLEncoder.encode(callback, "UTF-8"); - } catch (UnsupportedEncodingException e) { - return authUrl+"/authorization?response_type=code&client_id=" - + clientId + "&redirect_uri=" + callback; - } - } + this.accessToken = object.get("access_token").getAsString(); - public void authorize(String code, String redirectUri) throws AuthorizationFailure { - FluentStringsMap params = new FluentStringsMap(); + JsonElement jsonElement = object.get("refresh_token"); + this.refreshToken = jsonElement != null ? object.get( + "refresh_token").getAsString() : null; + /** News **/ + JsonElement jsonElementExpires = object.get("expires_in"); + this.expiresIn = jsonElementExpires != null ? Long.parseLong(object.get( + "expires_in").getAsString()) : null; - params.add("grant_type", "authorization_code"); - params.add("client_id", String.valueOf(clientId)); - params.add("client_secret", clientSecret); - params.add("code", code); - params.add("redirect_uri", redirectUri); + JsonElement jsonElementScope = object.get("scope"); + this.scope = jsonElementScope != null ? object.get( + "scope").getAsString() : null; - BoundRequestBuilder r = preparePost("/oauth/token", params); + JsonElement jsonElementUserID = object.get("user_id"); + this.userId = jsonElementUserID != null ? object.get( + "user_id").getAsString() : null; - parseToken(r); - } + JsonElement jsonElementToken = object.get("token_type"); + this.tokenType = jsonElementToken != null ? object.get( + "token_type").getAsString() : null; - private void parseToken(BoundRequestBuilder r) throws AuthorizationFailure { - Response response = null; - String responseBody = ""; - try { - response = r.execute().get(); - responseBody = response.getResponseBody(); - } catch (InterruptedException e) { - throw new AuthorizationFailure(e); - } catch (ExecutionException e) { - throw new AuthorizationFailure(e); - } catch (IOException e) { - throw new AuthorizationFailure(e); - } - - JsonParser p = new JsonParser(); - JsonObject object; - - try { - object = p.parse(responseBody).getAsJsonObject(); - } catch (JsonSyntaxException e) { - throw new AuthorizationFailure(responseBody); - } - - if (response.getStatusCode() == 200) { - - this.accessToken = object.get("access_token").getAsString(); - - JsonElement jsonElement = object.get("refresh_token"); - this.refreshToken = jsonElement != null ? object.get( - "refresh_token").getAsString() : null; - /** News **/ - JsonElement jsonElementExpires = object.get("expires_in"); - this.expiresIn = jsonElementExpires != null ? Long.parseLong(object.get( - "expires_in").getAsString()): null; - - JsonElement jsonElementScope = object.get("scope"); - this.scope = jsonElementScope != null ? object.get( - "scope").getAsString() : null; - - JsonElement jsonElementUserID = object.get("user_id"); - this.userId = jsonElementUserID != null ? object.get( - "user_id").getAsString() : null; - - JsonElement jsonElementToken = object.get("token_type"); - this.tokenType = jsonElementToken != null ? object.get( - "token_type").getAsString() : null; - - } else { - throw new AuthorizationFailure(object.get("message").getAsString()); - } - - } - - private boolean hasRefreshToken() { - return this.refreshToken != null && !this.refreshToken.isEmpty(); + } else { + throw new AuthorizationFailure(object.get("message").getAsString()); } - public Response post(String path, FluentStringsMap params, String body) throws MeliException { - - BoundRequestBuilder r = preparePost(path, params, body); - - Response response; - try { - response = r.execute().get(); - } catch (Exception e) { - throw new MeliException(e); - } - - - return response; - } - - public Response put(String path, FluentStringsMap params, String body) throws MeliException { - - BoundRequestBuilder r = preparePut(path, params, body); - - Response response; - try { - response = r.execute().get(); - } catch (Exception e) { - throw new MeliException(e); - } - - return response; - } - - public Response delete(String path, FluentStringsMap params) throws MeliException { - BoundRequestBuilder r = prepareDelete(path, params); - - Response response; - try { - response = r.execute().get(); - } catch (Exception e) { - throw new MeliException(e); - } - - return response; - } - - public BoundRequestBuilder head(String path) { - return null; - } + } - public BoundRequestBuilder options(String path) { - return null; - } } diff --git a/src/main/java/com/mercadolibre/sdk/dao/MeliHttpDao.java b/src/main/java/com/mercadolibre/sdk/dao/MeliHttpDao.java new file mode 100644 index 0000000..877c226 --- /dev/null +++ b/src/main/java/com/mercadolibre/sdk/dao/MeliHttpDao.java @@ -0,0 +1,21 @@ +package com.mercadolibre.sdk.dao; + +import com.mercadolibre.sdk.exception.MeliException; +import com.ning.http.client.FluentStringsMap; +import com.ning.http.client.Response; + +public interface MeliHttpDao { + + Response get(String path) throws MeliException; + + Response get(String path, FluentStringsMap params) throws MeliException; + + Response post(String path, FluentStringsMap params) throws MeliException; + + Response post(String path, FluentStringsMap params, String body) throws MeliException; + + Response put(String path, FluentStringsMap params, String body) throws MeliException; + + Response delete(String path, FluentStringsMap params) throws MeliException; + +} diff --git a/src/main/java/com/mercadolibre/sdk/dao/impl/MeliHttpDaoImpl.java b/src/main/java/com/mercadolibre/sdk/dao/impl/MeliHttpDaoImpl.java new file mode 100644 index 0000000..531c991 --- /dev/null +++ b/src/main/java/com/mercadolibre/sdk/dao/impl/MeliHttpDaoImpl.java @@ -0,0 +1,141 @@ +package com.mercadolibre.sdk.dao.impl; + +import com.mercadolibre.sdk.exception.MeliException; +import com.mercadolibre.sdk.dao.MeliHttpDao; +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder; +import com.ning.http.client.FluentStringsMap; +import com.ning.http.client.Response; + +public class MeliHttpDaoImpl implements MeliHttpDao { + + public static String apiUrl = "https://api.mercadolibre.com"; + + private AsyncHttpClient httpClient; + + public MeliHttpDaoImpl() { + } + + public MeliHttpDaoImpl(AsyncHttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public Response get(String path) throws MeliException { + return get(path, new FluentStringsMap()); + } + + @Override + public Response get(String path, FluentStringsMap params) throws MeliException { + BoundRequestBuilder r = prepareGet(path, params); + + Response response; + try { + response = r.execute().get(); + } catch (Exception e) { + throw new MeliException(e); + } + + return response; + } + + @Override + public Response post(String path, FluentStringsMap params) throws MeliException { + BoundRequestBuilder r = preparePost(path, params); + + Response response; + try { + response = r.execute().get(); + } catch (Exception e) { + throw new MeliException(e); + } + + return response; + } + + @Override + public Response post(String path, FluentStringsMap params, String body) throws MeliException { + BoundRequestBuilder r = preparePost(path, params, body); + + Response response; + try { + response = r.execute().get(); + } catch (Exception e) { + throw new MeliException(e); + } + + return response; + } + + @Override + public Response put(String path, FluentStringsMap params, String body) throws MeliException { + BoundRequestBuilder r = preparePut(path, params, body); + + Response response; + try { + response = r.execute().get(); + } catch (Exception e) { + throw new MeliException(e); + } + + return response; + } + + @Override + public Response delete(String path, FluentStringsMap params) throws MeliException { + BoundRequestBuilder r = prepareDelete(path, params); + + Response response; + try { + response = r.execute().get(); + } catch (Exception e) { + throw new MeliException(e); + } + + return response; + } + + private BoundRequestBuilder prepareGet(String path, FluentStringsMap params) { + return httpClient.prepareGet(apiUrl + path) + .addHeader("Accept", "application/json") + .setQueryParameters(params); + } + + + private BoundRequestBuilder prepareDelete(String path, + FluentStringsMap params) { + return httpClient.prepareDelete(apiUrl + path) + .addHeader("Accept", "application/json") + .setQueryParameters(params); + } + + private BoundRequestBuilder preparePost(String path, + FluentStringsMap params, String body) { + return httpClient.preparePost(apiUrl + path) + .addHeader("Accept", "application/json") + .setQueryParameters(params) + .setHeader("Content-Type", "application/json") + .setBody(body) + .setBodyEncoding("UTF-8"); + } + + private BoundRequestBuilder preparePost(String path, FluentStringsMap params) { + return httpClient.preparePost(apiUrl + path) + .addHeader("Accept", "application/json") + .setQueryParameters(params); + } + + private BoundRequestBuilder preparePut(String path, + FluentStringsMap params, String body) { + return httpClient.preparePut(apiUrl + path) + .addHeader("Accept", "application/json") + .setQueryParameters(params) + .setHeader("Content-Type", "application/json").setBody(body) + .setBodyEncoding("UTF-8"); + } + + public void setHttpClient(AsyncHttpClient httpClient) { + this.httpClient = httpClient; + } + +} diff --git a/src/main/java/com/mercadolibre/sdk/AuthorizationFailure.java b/src/main/java/com/mercadolibre/sdk/exception/AuthorizationFailure.java similarity index 71% rename from src/main/java/com/mercadolibre/sdk/AuthorizationFailure.java rename to src/main/java/com/mercadolibre/sdk/exception/AuthorizationFailure.java index 38a06ae..96e83c9 100644 --- a/src/main/java/com/mercadolibre/sdk/AuthorizationFailure.java +++ b/src/main/java/com/mercadolibre/sdk/exception/AuthorizationFailure.java @@ -1,14 +1,15 @@ -package com.mercadolibre.sdk; +package com.mercadolibre.sdk.exception; public class AuthorizationFailure extends Exception { + private static final long serialVersionUID = 8688100047490895706L; public AuthorizationFailure(String message) { - super(message); + super(message); - } + } public AuthorizationFailure(Throwable cause) { - super(cause); + super(cause); } } diff --git a/src/main/java/com/mercadolibre/sdk/MeliException.java b/src/main/java/com/mercadolibre/sdk/exception/MeliException.java similarity index 73% rename from src/main/java/com/mercadolibre/sdk/MeliException.java rename to src/main/java/com/mercadolibre/sdk/exception/MeliException.java index 9650965..5c71f4d 100644 --- a/src/main/java/com/mercadolibre/sdk/MeliException.java +++ b/src/main/java/com/mercadolibre/sdk/exception/MeliException.java @@ -1,8 +1,9 @@ -package com.mercadolibre.sdk; +package com.mercadolibre.sdk.exception; public class MeliException extends Exception { + public MeliException(Throwable cause) { - super(cause); + super(cause); } private static final long serialVersionUID = 7263275678852231779L; diff --git a/src/test/java/com/mercadolibre/sdk/MeliTest.java b/src/test/java/com/mercadolibre/sdk/MeliTest.java index b8fe0dc..84643ca 100644 --- a/src/test/java/com/mercadolibre/sdk/MeliTest.java +++ b/src/test/java/com/mercadolibre/sdk/MeliTest.java @@ -1,147 +1,184 @@ package com.mercadolibre.sdk; -import java.io.IOException; - -import org.junit.Assert; -import org.junit.Test; - +import com.mercadolibre.sdk.dao.MeliHttpDao; +import com.mercadolibre.sdk.dao.impl.MeliHttpDaoImpl; +import com.mercadolibre.sdk.exception.AuthorizationFailure; +import com.mercadolibre.sdk.exception.MeliException; +import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.FluentStringsMap; +import com.ning.http.client.ListenableFuture; import com.ning.http.client.Response; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; -public class MeliTest extends Assert { - @Test - public void testGetAuthUrl() { - assertEquals( - "https://auth.mercadolibre.com.ar/authorization?response_type=code&client_id=123456&redirect_uri=http%3A%2F%2Fsomeurl.com", - new Meli(123456l, "client secret") - .getAuthUrl("http://someurl.com", Meli.AuthUrls.MLA)); - } +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.ExecutionException; - @Test(expected = AuthorizationFailure.class) - public void testAuthorizationFailure() throws AuthorizationFailure { +import static org.junit.Assert.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; - Meli.apiUrl = "https://api.mercadolibre.com"; +public class MeliTest { - new Meli(123456l, "client secret").authorize("bad code", - "http://someurl.com"); + private enum HttpMethod { + GET, POST, PUT, DELETE } - @Test - public void testAuthorizationSuccess() throws AuthorizationFailure { - - Meli.apiUrl = "https://api.mercadolibre.com"; - Meli m = new Meli(123456l, "client secret"); - m.authorize("valid code with refresh token", "http://someurl.com"); + private Meli meli; + private MeliHttpDao meliHttpDao; - assertEquals("valid token", m.getAccessToken()); - assertEquals("valid refresh token", m.getRefreshToken()); + @Before + public void setUp() { + meli = new Meli(1234561L, "client secret", "valid token"); + meliHttpDao = new MeliHttpDaoImpl(); + meli.setMeliHttpDao(meliHttpDao); } @Test - public void testGet() throws MeliException, IOException { - Meli.apiUrl = "https://api.mercadolibre.com"; - Meli m = new Meli(123456l, "client secret", "valid token"); + public void getAuthUrl_returnsAuthUrl() { + String authUrl = meli.getAuthUrl("http://someurl.com", Meli.AuthUrls.MLA); - Response response = m.get("/sites"); - - assertEquals(200, response.getStatusCode()); - assertFalse(response.getResponseBody().isEmpty()); + assertEquals("https://auth.mercadolibre.com.ar/authorization?response_type=code&client_id=1234561&redirect_uri=http%3A%2F%2Fsomeurl.com", authUrl); } @Test - public void testGetWithRefreshToken() throws MeliException, IOException { - Meli.apiUrl = "https://api.mercadolibre.com"; - Meli m = new Meli(123456l, "client secret", "expired token", - "valid refresh token"); + public void authorize_withValidCode_returnsAccessToken() throws AuthorizationFailure, IOException, ExecutionException, InterruptedException, MeliException { + String jsonResponse = getFileContent("authorization_success.json"); + int statusCode = 200; + MeliHttpDaoImpl.apiUrl = "https://api.mercadolibre.com"; + mockHttpRequest(jsonResponse, statusCode, HttpMethod.POST, null); - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response response = m.get("/users/me", params); + meli.authorize("valid code with refresh token", "http://someurl.com"); - assertEquals(200, response.getStatusCode()); - assertFalse(response.getResponseBody().isEmpty()); + assertEquals("APP_USR-6092-3246532-cb45c82853f6e620bb0deda096b128d3-8035443", meli.getAccessToken()); + assertEquals("TG-5005b6b3e4b07e60756a3353", meli.getRefreshToken()); } - @Test - public void testErrorHandling() throws IOException, MeliException { - Meli m = new Meli(123456l, "client secret", "invalid token"); + @Test(expected = AuthorizationFailure.class) + public void authorize_withInvalidCode_throwsAuthorizationFailureException() throws AuthorizationFailure, IOException, ExecutionException, InterruptedException, MeliException { + String jsonResponse = getFileContent("authorization_bad_request.json"); + int statusCode = 400; + MeliHttpDaoImpl.apiUrl = "https://api.mercadolibre.com"; + mockHttpRequest(jsonResponse, statusCode, HttpMethod.POST, null); - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response response = m.get("/users/me", params); - assertEquals(403, response.getStatusCode()); + meli.authorize("bad code", "http://someurl.com"); } @Test - public void testUserAgent() throws IOException, MeliException { - Meli m = new Meli(123456l, "client secret", "invalid token"); + public void get_withExistingEndpointAndNoParams_returnsSuccessfulResponse() throws MeliException, IOException, ExecutionException, InterruptedException { + String jsonResponse = getFileContent("get_sites_success.json"); + MeliHttpDaoImpl.apiUrl = "https://api.mercadolibre.com"; + int statusCode = 200; + mockHttpRequest(jsonResponse, statusCode, HttpMethod.GET, null); - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response response = m.get("/echo/user_agent", params); - assertEquals(200, response.getStatusCode()); - } - - public void testPost() throws MeliException { - Meli m = new Meli(123456l, "client secret", "valid token"); + Response response = meli.get("/sites"); - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response r = m.post("/items", params, "{\"foo\":\"bar\"}"); - - assertEquals(201, r.getStatusCode()); + assertEquals(200, response.getStatusCode()); + assertEquals(jsonResponse, response.getResponseBody()); } - public void testPostWithRefreshToken() throws MeliException { - Meli m = new Meli(123456l, "client secret", "expired token", - "valid refresh token"); - - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response r = m.post("/items", params, "{\"foo\":\"bar\"}"); - - assertEquals(201, r.getStatusCode()); + @Test + public void get_withExistingEndpointAndParams_returnsSuccessfulResponse() throws MeliException, IOException, ExecutionException, InterruptedException { + String jsonResponse = getFileContent("get_custid_success.json"); + MeliHttpDaoImpl.apiUrl = "https://api.mercadolibre.com"; + int statusCode = 200; + mockHttpRequest(jsonResponse, statusCode, HttpMethod.GET, null); + FluentStringsMap params = new FluentStringsMap(); + params.add("access_token", meli.getAccessToken()); + + Response response = meli.get("/sites", params); + + assertEquals(200, response.getStatusCode()); + assertEquals(jsonResponse, response.getResponseBody()); } - public void testPut() throws MeliException { - Meli m = new Meli(123456l, "client secret", "valid token"); - - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response r = m.put("/items/123", params, "{\"foo\":\"bar\"}"); - - assertEquals(200, r.getStatusCode()); + @Test + public void post_withExistingEndpoint_returnsSuccessfulResponse() throws MeliException, IOException, ExecutionException, InterruptedException { + String jsonResponse = getFileContent("post_item_success.json"); + FluentStringsMap params = new FluentStringsMap(); + params.add("access_token", meli.getAccessToken()); + int statusCode = 201; + String body = "{\"foo\":\"bar\"}"; + mockHttpRequest(jsonResponse, statusCode, HttpMethod.POST, body); + + Response response = meli.post("/items", params, body); + + assertEquals(201, response.getStatusCode()); + assertEquals(jsonResponse, response.getResponseBody()); } - public void testPutWithRefreshToken() throws MeliException { - Meli m = new Meli(123456l, "client secret", "expired token", - "valid refresh token"); + @Test + public void put_withExistingItem_returnsSuccessfulResponse() throws MeliException, InterruptedException, ExecutionException, IOException { + int statusCode = 200; + String body = "{\"tags\":[\"immediate_payment\"]}"; + mockHttpRequest("", statusCode, HttpMethod.PUT, body); - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response r = m.put("/items/123", params, "{\"foo\":\"bar\"}"); + FluentStringsMap params = new FluentStringsMap(); + params.add("access_token", meli.getAccessToken()); + Response response = meli.put("/items/123", params, "{\"tags\":[\"immediate_payment\"]}"); - assertEquals(200, r.getStatusCode()); + assertEquals(200, response.getStatusCode()); } - public void testDelete() throws MeliException { - Meli m = new Meli(123456l, "client secret", "valid token"); + @Test + public void delete_WithExistingItem_returnsSuccessfulResponse() throws MeliException, InterruptedException, ExecutionException, IOException { + int statusCode = 200; + FluentStringsMap params = new FluentStringsMap(); + params.add("access_token", meli.getAccessToken()); + mockHttpRequest("", statusCode, HttpMethod.DELETE, null); - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response r = m.delete("/items/123", params); + Response response = meli.delete("/items/123", params); - assertEquals(200, r.getStatusCode()); + assertEquals(200, response.getStatusCode()); } - public void testDeleteWithRefreshToken() throws MeliException { - Meli m = new Meli(123456l, "client secret", "expired token", - "valid refresh token"); - - FluentStringsMap params = new FluentStringsMap(); - params.add("access_token", m.getAccessToken()); - Response r = m.delete("/items/123", params); + private String getFileContent(String filename) throws IOException { + InputStream inputStream = new FileInputStream("src/test/resources/api_responses/" + filename); + return IOUtils.toString(inputStream, "UTF-8"); + } - assertEquals(200, r.getStatusCode()); + private void mockHttpRequest(String jsonResponse, int statusCode, HttpMethod httpMethod, String body) throws IOException, ExecutionException, InterruptedException { + Response responseMock = mock(Response.class); + given(responseMock.getStatusCode()).willReturn(statusCode); + given(responseMock.getResponseBody()).willReturn(jsonResponse); + + ListenableFuture listenableFutureMock = mock(ListenableFuture.class); + given(listenableFutureMock.get()).willReturn(responseMock); + + AsyncHttpClient.BoundRequestBuilder boundRequestBuilderMock = mock(AsyncHttpClient.BoundRequestBuilder.class); + given(boundRequestBuilderMock.addHeader(anyString(), anyString())).willReturn(boundRequestBuilderMock); + given(boundRequestBuilderMock.setQueryParameters(any(FluentStringsMap.class))).willReturn(boundRequestBuilderMock); + + if (body != null) { + given(boundRequestBuilderMock.setHeader(anyString(), anyString())).willReturn(boundRequestBuilderMock); + given(boundRequestBuilderMock.setBody(body)).willReturn(boundRequestBuilderMock); + given(boundRequestBuilderMock.setBodyEncoding(anyString())).willReturn(boundRequestBuilderMock); + } + + given(boundRequestBuilderMock.execute()).willReturn(listenableFutureMock); + + AsyncHttpClient asyncHttpClientMock = mock(AsyncHttpClient.class); + switch (httpMethod) { + case GET: + given(asyncHttpClientMock.prepareGet(anyString())).willReturn(boundRequestBuilderMock); + break; + case POST: + given(asyncHttpClientMock.preparePost(anyString())).willReturn(boundRequestBuilderMock); + break; + case PUT: + given(asyncHttpClientMock.preparePut(anyString())).willReturn(boundRequestBuilderMock); + break; + case DELETE: + given(asyncHttpClientMock.prepareDelete(anyString())).willReturn(boundRequestBuilderMock); + break; + } + + ((MeliHttpDaoImpl) meliHttpDao).setHttpClient(asyncHttpClientMock); } + } \ No newline at end of file diff --git a/src/test/resources/api_responses/authorization_bad_request.json b/src/test/resources/api_responses/authorization_bad_request.json new file mode 100644 index 0000000..01c5abe --- /dev/null +++ b/src/test/resources/api_responses/authorization_bad_request.json @@ -0,0 +1,6 @@ +{ + "message": "invalid client_id or client_secret.", + "error": "invalid_client", + "status": 400, + "cause": [] +} \ No newline at end of file diff --git a/src/test/resources/api_responses/authorization_success.json b/src/test/resources/api_responses/authorization_success.json new file mode 100644 index 0000000..6720277 --- /dev/null +++ b/src/test/resources/api_responses/authorization_success.json @@ -0,0 +1,7 @@ +{ + "access_token" : "APP_USR-6092-3246532-cb45c82853f6e620bb0deda096b128d3-8035443", + "token_type" : "bearer", + "expires_in" : 10800, + "refresh_token" : "TG-5005b6b3e4b07e60756a3353", + "scope" : "write read" +} \ No newline at end of file diff --git a/src/test/resources/api_responses/get_custid_success.json b/src/test/resources/api_responses/get_custid_success.json new file mode 100644 index 0000000..ea7e022 --- /dev/null +++ b/src/test/resources/api_responses/get_custid_success.json @@ -0,0 +1,140 @@ +{ + "id": 206946886, + "nickname": "TETE6838590", + "registration_date": "2016-02-24T15: 18: 42.000-04: 00", + "first_name": "Pedro", + "last_name": "Picapiedras", + "country_id": "AR", + "email": "test_user_15879541@testuser.com", + "identification": { + "type": "DNI", + "number": "33333333" + }, + "address": { + "state": "AR-C", + "city": "CapitalFederal", + "address": "Triunvirato5555", + "zip_code": "1414" + }, + "phone": { + "area_code": "011", + "number": "4444-4444", + "extension": "001", + "verified": false + }, + "alternative_phone": { + "area_code": "", + "number": "", + "extension": "" + }, + "user_type": "normal", + "tags": [ + "normal", + "test_user", + "user_info_verified" + ], + "logo": null, + "points": 100, + "site_id": "MLA", + "permalink": "http: //perfil.mercadolibre.com.ar/TETE6838590", + "shipping_modes": [ + "custom", + "not_specified" + ], + "seller_experience": "ADVANCED", + "seller_reputation": { + "level_id": null, + "power_seller_status": null, + "transactions": { + "period": "historic", + "total": 0, + "completed": 0, + "canceled": 0, + "ratings": { + "positive": 0, + "negative": 0, + "neutral": 0 + } + } + }, + "buyer_reputation": { + "canceled_transactions": 0, + "transactions": { + "period": "historic", + "total": null, + "completed": null, + "canceled": { + "total": null, + "paid": null + }, + "unrated": { + "total": null, + "paid": null + }, + "not_yet_rated": { + "total": null, + "paid": null, + "units": null + } + }, + "tags": [ + + ] + }, + "status": { + "site_status": "active", + "list": { + "allow": true, + "codes": [ + + ], + "immediate_payment": { + "required": false, + "reasons": [ + + ] + } + }, + "buy": { + "allow": true, + "codes": [ + + ], + "immediate_payment": { + "required": false, + "reasons": [ + + ] + } + }, + "sell": { + "allow": true, + "codes": [ + + ], + "immediate_payment": { + "required": false, + "reasons": [ + + ] + } + }, + "billing": { + "allow": true, + "codes": [ + + ] + }, + "mercadopago_tc_accepted": true, + "mercadopago_account_type": "personal", + "mercadoenvios": "not_accepted", + "immediate_payment": false, + "confirmed_email": false, + "user_type": "simple_registration", + "required_action": "" + }, + "credit": { + "consumed": 100, + "credit_level_id": "MLA1" + } +} \ No newline at end of file diff --git a/src/test/resources/api_responses/get_sites_success.json b/src/test/resources/api_responses/get_sites_success.json new file mode 100644 index 0000000..358bd3b --- /dev/null +++ b/src/test/resources/api_responses/get_sites_success.json @@ -0,0 +1,82 @@ +[ + { + "id": "MLA", + "name": "Argentina" + }, + { + "id": "MLC", + "name": "Chile" + }, + { + "id": "MLU", + "name": "Uruguay" + }, + { + "id": "MPY", + "name": "Paraguay" + }, + { + "id": "MBO", + "name": "Bolivia" + }, + { + "id": "MHN", + "name": "Honduras" + }, + { + "id": "MCU", + "name": "Cuba" + }, + { + "id": "MGT", + "name": "Guatemala" + }, + { + "id": "MCO", + "name": "Colombia" + }, + { + "id": "MCR", + "name": "Costa Rica" + }, + { + "id": "MRD", + "name": "Dominicana" + }, + { + "id": "MSV", + "name": "El Salvador" + }, + { + "id": "MEC", + "name": "Ecuador" + }, + { + "id": "MPA", + "name": "Panamá" + }, + { + "id": "MLV", + "name": "Venezuela" + }, + { + "id": "MNI", + "name": "Nicaragua" + }, + { + "id": "MPE", + "name": "Perú" + }, + { + "id": "MPT", + "name": "Portugal" + }, + { + "id": "MLB", + "name": "Brasil" + }, + { + "id": "MLM", + "name": "Mexico" + } +] \ No newline at end of file diff --git a/src/test/resources/api_responses/post_item_success.json b/src/test/resources/api_responses/post_item_success.json new file mode 100644 index 0000000..89e62ca --- /dev/null +++ b/src/test/resources/api_responses/post_item_success.json @@ -0,0 +1,139 @@ +{ + "id": "MLA600190449", + "site_id": "MLA", + "title": "Iphone 6 64gb Space Gray Liberado", + "subtitle": null, + "seller_id": 118617944, + "category_id": "MLA352543", + "official_store_id": null, + "price": 16550, + "base_price": 16550, + "original_price": null, + "currency_id": "ARS", + "initial_quantity": 2, + "available_quantity": 2, + "sold_quantity": 0, + "buying_mode": "buy_it_now", + "listing_type_id": "bronze", + "start_time": "2016-01-13T18:10:29.000Z", + "stop_time": "2016-03-13T18:10:29.000Z", + "condition": "new", + "permalink": "http://articulo.mercadolibre.com.ar/MLA-600190449-iphone-6-64gb-space-gray-liberado-_JM", + "thumbnail": "http://mla-s1-p.mlstatic.com/873411-MLA20547233702_012016-I.jpg", + "secure_thumbnail": "https://a248.e.akamai.net/mla-s1-p.mlstatic.com/873411-MLA20547233702_012016-I.jpg", + "pictures": [ + { + "id": "873411-MLA20547233702_012016", + "url": "http://mla-s1-p.mlstatic.com/873411-MLA20547233702_012016-O.jpg", + "secure_url": "https://a248.e.akamai.net/mla-s1-p.mlstatic.com/873411-MLA20547233702_012016-O.jpg", + "size": "225x225", + "max_size": "225x225", + "quality": "" + }, + { + "id": "234411-MLA20547233720_012016", + "url": "http://mla-s1-p.mlstatic.com/234411-MLA20547233720_012016-O.jpg", + "secure_url": "https://a248.e.akamai.net/mla-s1-p.mlstatic.com/234411-MLA20547233720_012016-O.jpg", + "size": "259x194", + "max_size": "259x194", + "quality": "" + }, + { + "id": "768311-MLA20547233735_012016", + "url": "http://mla-s1-p.mlstatic.com/768311-MLA20547233735_012016-O.jpg", + "secure_url": "https://a248.e.akamai.net/mla-s1-p.mlstatic.com/768311-MLA20547233735_012016-O.jpg", + "size": "300x168", + "max_size": "300x168", + "quality": "" + } + ], + "video_id": null, + "descriptions": [ + { + "id": "MLA600190449-1007729488" + } + ], + "accepts_mercadopago": true, + "non_mercado_pago_payment_methods": [ + ], + "shipping": { + "mode": "me2", + "local_pick_up": true, + "free_shipping": true, + "free_methods": [ + { + "id": 73328, + "rule": { + "free_mode": "country", + "value": null + } + } + ], + "dimensions": null, + "tags": [ + ] + }, + "international_delivery_mode": "none", + "seller_address": { + "id": 138834162, + "comment": "", + "address_line": "", + "zip_code": "", + "city": { + "id": "TUxBQ05FVXF1ZW4", + "name": "Neuquén" + }, + "state": { + "id": "AR-Q", + "name": "Neuquén" + }, + "country": { + "id": "AR", + "name": "Argentina" + }, + "latitude": -38.95628353, + "longitude": -68.12749595, + "search_location": { + "neighborhood": { + "id": "", + "name": "" + }, + "city": { + "id": "TUxBQ05FVXF1ZW4", + "name": "Neuquén" + }, + "state": { + "id": "TUxBUE5FVW4xMzMzNQ", + "name": "Neuquén" + } + } + }, + "seller_contact": null, + "location": { + }, + "geolocation": { + "latitude": -38.96055205, + "longitude": -68.12525497 + }, + "coverage_areas": [ + ], + "attributes": [ + ], + "listing_source": "", + "variations": [ + ], + "status": "active", + "sub_status": [ + ], + "tags": [ + ], + "warranty": null, + "catalog_product_id": null, + "parent_item_id": null, + "differential_pricing": null, + "deal_ids": [ + ], + "automatic_relist": false, + "date_created": "2016-01-13T18:10:29.000Z", + "last_updated": "2016-01-13T18:26:54.000Z" +} \ No newline at end of file