mirror of
https://github.com/cubixle/ebay.git
synced 2026-04-24 19:54:47 +01:00
Add more GetBidding test and IsError
This commit is contained in:
2
Makefile
2
Makefile
@@ -4,4 +4,4 @@ test:
|
||||
|
||||
.PHONY: integration
|
||||
integration:
|
||||
go test -count=1 -v -run "Auction" ./test/integration -integration=true
|
||||
go test -count=1 -v -run "Auction" ./test/integration -integration=true -timeout=999999s
|
||||
@@ -160,7 +160,7 @@ type LegacyItem struct {
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/buy/browse/resources/item/methods/getItemByLegacyId
|
||||
func (s *BrowseService) GetItemByLegacyID(ctx context.Context, itemLegacyID string, opts ...Opt) (CompactItem, error) {
|
||||
u := fmt.Sprintf("buy/browse/v1/item/get_item_by_legacy_id?legacy_item_id=%s", itemLegacyID)
|
||||
req, err := s.client.NewRequest(http.MethodGet, u, opts...)
|
||||
req, err := s.client.NewRequest(http.MethodGet, u, nil, opts...)
|
||||
if err != nil {
|
||||
return CompactItem{}, err
|
||||
}
|
||||
@@ -190,7 +190,7 @@ type CompactItem struct {
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/buy/browse/resources/item/methods/getItem
|
||||
func (s *BrowseService) GetCompactItem(ctx context.Context, itemID string, opts ...Opt) (CompactItem, error) {
|
||||
u := fmt.Sprintf("buy/browse/v1/item/%s?fieldgroups=COMPACT", itemID)
|
||||
req, err := s.client.NewRequest(http.MethodGet, u, opts...)
|
||||
req, err := s.client.NewRequest(http.MethodGet, u, nil, opts...)
|
||||
if err != nil {
|
||||
return CompactItem{}, err
|
||||
}
|
||||
@@ -368,7 +368,7 @@ type Item struct {
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/buy/browse/resources/item/methods/getItem
|
||||
func (s *BrowseService) GetItem(ctx context.Context, itemID string, opts ...Opt) (Item, error) {
|
||||
u := fmt.Sprintf("buy/browse/v1/item/%s?fieldgroups=PRODUCT", itemID)
|
||||
req, err := s.client.NewRequest(http.MethodGet, u, opts...)
|
||||
req, err := s.client.NewRequest(http.MethodGet, u, nil, opts...)
|
||||
if err != nil {
|
||||
return Item{}, err
|
||||
}
|
||||
|
||||
80
ebay.go
80
ebay.go
@@ -1,9 +1,11 @@
|
||||
package ebay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
@@ -78,7 +80,7 @@ type Opt func(*http.Request)
|
||||
|
||||
// NewRequest creates an API request.
|
||||
// url should always be specified without a preceding slash.
|
||||
func (c *Client) NewRequest(method, url string, opts ...Opt) (*http.Request, error) {
|
||||
func (c *Client) NewRequest(method, url string, body interface{}, opts ...Opt) (*http.Request, error) {
|
||||
if strings.HasPrefix(url, "/") {
|
||||
return nil, errors.New("url should always be specified without a preceding slash")
|
||||
}
|
||||
@@ -86,7 +88,16 @@ func (c *Client) NewRequest(method, url string, opts ...Opt) (*http.Request, err
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
req, err := http.NewRequest(method, u.String(), nil)
|
||||
var buf io.ReadWriter
|
||||
if body != nil {
|
||||
buf = new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
req, err := http.NewRequest(method, u.String(), buf)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
@@ -112,30 +123,36 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) error
|
||||
return errors.WithStack(json.NewDecoder(resp.Body).Decode(v))
|
||||
}
|
||||
|
||||
// ErrorData reports one or more errors caused by an API request.
|
||||
// Error describes one error caused by an eBay API request.
|
||||
//
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/static/handling-error-messages.html
|
||||
type Error struct {
|
||||
ErrorID int `json:"errorId,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
SubDomain string `json:"subDomain,omitempty"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
LongMessage string `json:"longMessage,omitempty"`
|
||||
InputRefIds []string `json:"inputRefIds,omitempty"`
|
||||
OuputRefIds []string `json:"outputRefIds,omitempty"`
|
||||
Parameters []struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
} `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// ErrorData describes one or more errors caused by an eBay API request.
|
||||
//
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/static/handling-error-messages.html
|
||||
type ErrorData struct {
|
||||
Errors []struct {
|
||||
ErrorID int `json:"errorId,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
SubDomain string `json:"subDomain,omitempty"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
LongMessage string `json:"longMessage,omitempty"`
|
||||
InputRefIds []string `json:"inputRefIds,omitempty"`
|
||||
OuputRefIds []string `json:"outputRefIds,omitempty"`
|
||||
Parameters []struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
} `json:"parameters,omitempty"`
|
||||
} `json:"errors,omitempty"`
|
||||
Response *http.Response
|
||||
RequestDump string
|
||||
Errors []Error `json:"errors,omitempty"`
|
||||
|
||||
response *http.Response
|
||||
requestDump string
|
||||
}
|
||||
|
||||
func (e *ErrorData) Error() string {
|
||||
return fmt.Sprintf("%d %s: %+v", e.Response.StatusCode, e.RequestDump, e.Errors)
|
||||
return fmt.Sprintf("%d %s: %+v", e.response.StatusCode, e.requestDump, e.Errors)
|
||||
}
|
||||
|
||||
// CheckResponse checks the API response for errors, and returns them if present.
|
||||
@@ -144,7 +161,28 @@ func CheckResponse(req *http.Request, resp *http.Response) error {
|
||||
return nil
|
||||
}
|
||||
dump, _ := httputil.DumpRequest(req, true)
|
||||
errorData := &ErrorData{Response: resp, RequestDump: string(dump)}
|
||||
errorData := &ErrorData{response: resp, requestDump: string(dump)}
|
||||
_ = json.NewDecoder(resp.Body).Decode(errorData)
|
||||
return errorData
|
||||
}
|
||||
|
||||
// IsError allows to check if err is a specific error codes returned by the eBay API.
|
||||
//
|
||||
// eBay API docs: https://developer.ebay.com/devzone/xml/docs/Reference/ebay/Errors/errormessages.htm
|
||||
func IsError(err error, codes ...int) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
errData, ok := err.(*ErrorData)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, e := range errData.Errors {
|
||||
for _, code := range codes {
|
||||
if e.ErrorID == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
48
ebay_test.go
48
ebay_test.go
@@ -10,15 +10,28 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/jybp/ebay"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// setup sets up a test HTTP server.
|
||||
func setup(t *testing.T) (client *ebay.Client, mux *http.ServeMux, teardown func()) {
|
||||
mux = http.NewServeMux()
|
||||
server := httptest.NewServer(mux)
|
||||
var err error
|
||||
client, err = ebay.NewCustomClient(nil, server.URL+"/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return client, mux, server.Close
|
||||
}
|
||||
|
||||
func TestNewRequest(t *testing.T) {
|
||||
testOpt := func(r *http.Request) {
|
||||
r.URL.RawQuery = "q=1"
|
||||
}
|
||||
client, _ := ebay.NewCustomClient(nil, "https://api.ebay.com/")
|
||||
r, _ := client.NewRequest(http.MethodPost, "test", testOpt)
|
||||
r, _ := client.NewRequest(http.MethodPost, "test", nil, testOpt)
|
||||
assert.Equal(t, "https://api.ebay.com/test?q=1", fmt.Sprint(r.URL))
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
}
|
||||
@@ -69,14 +82,29 @@ func TestCheckResponse(t *testing.T) {
|
||||
assert.Equal(t, "2200077988|0", err.Errors[0].Parameters[0].Value)
|
||||
}
|
||||
|
||||
// setup sets up a test HTTP server
|
||||
func setup(t *testing.T) (client *ebay.Client, mux *http.ServeMux, teardown func()) {
|
||||
mux = http.NewServeMux()
|
||||
server := httptest.NewServer(mux)
|
||||
var err error
|
||||
client, err = ebay.NewCustomClient(nil, server.URL+"/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestIsErrorMatches(t *testing.T) {
|
||||
var err error = &ebay.ErrorData{
|
||||
Errors: []ebay.Error{
|
||||
ebay.Error{ErrorID: 1},
|
||||
},
|
||||
}
|
||||
return client, mux, server.Close
|
||||
assert.True(t, ebay.IsError(err, 1, 2, 3))
|
||||
}
|
||||
|
||||
func TestIsErrorNoMatches(t *testing.T) {
|
||||
var err error = &ebay.ErrorData{
|
||||
Errors: []ebay.Error{
|
||||
ebay.Error{ErrorID: 4},
|
||||
},
|
||||
}
|
||||
assert.False(t, ebay.IsError(err, 1, 2, 3))
|
||||
}
|
||||
|
||||
func TestIsErrorWrongType(t *testing.T) {
|
||||
var err error = errors.New("test")
|
||||
assert.False(t, ebay.IsError(err, 1, 2, 3))
|
||||
}
|
||||
|
||||
func TestIsErrorNil(t *testing.T) {
|
||||
assert.False(t, ebay.IsError(nil, 1, 2, 3))
|
||||
}
|
||||
|
||||
68
offer.go
68
offer.go
@@ -65,16 +65,80 @@ type Bidding struct {
|
||||
} `json:"currentProxyBid"`
|
||||
}
|
||||
|
||||
// GetBidding retrieves the buyer's bidding details on an auction.
|
||||
// Some valid eBay error codes for the GetBidding method.
|
||||
//
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/buy/offer/resources/bidding/methods/getBidding#h2-error-codes
|
||||
const (
|
||||
ErrGetBiddingMarketplaceNotSupported = 120017
|
||||
ErrGetBiddingNoBiddingActivity = 120033
|
||||
)
|
||||
|
||||
// GetBidding retrieves the buyer's bidding details on a specific auction item.
|
||||
//
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/buy/offer/resources/bidding/methods/getBidding
|
||||
func (s *OfferService) GetBidding(ctx context.Context, itemID, marketplaceID string, opts ...Opt) (Item, error) {
|
||||
u := fmt.Sprintf("buy/offer/v1_beta/bidding/%s", itemID)
|
||||
opts = append(opts, OptBuyMarketplace(marketplaceID))
|
||||
req, err := s.client.NewRequest(http.MethodGet, u, opts...)
|
||||
req, err := s.client.NewRequest(http.MethodGet, u, nil, opts...)
|
||||
if err != nil {
|
||||
return Item{}, err
|
||||
}
|
||||
var it Item
|
||||
return it, s.client.Do(ctx, req, &it)
|
||||
}
|
||||
|
||||
// ProxyBid represents an eBay proxy bid.
|
||||
type ProxyBid struct {
|
||||
ProxyBidID string `json:"proxyBidId"`
|
||||
}
|
||||
|
||||
// Some valid eBay error codes for the PlaceProxyBid method.
|
||||
//
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/buy/offer/resources/bidding/methods/getBidding#h2-error-codes
|
||||
const (
|
||||
ErrPlaceProxyBidAuctionEndedBecauseOfBuyItNow = 120002
|
||||
ErrPlaceProxyBidBidCannotBeGreaterThanBuyItNowPrice = 120005
|
||||
ErrPlaceProxyBidAmountTooHigh = 120007
|
||||
ErrPlaceProxyBidAmountTooLow = 120008
|
||||
ErrPlaceProxyBidCurrencyMustMatchItemPriceCurrency = 120009
|
||||
ErrPlaceProxyBidCannotLowerYourProxyBid = 120010
|
||||
ErrPlaceProxyBidAmountExceedsLimit = 120011
|
||||
ErrPlaceProxyBidAuctionHasEnded = 120012
|
||||
ErrPlaceProxyBidAmountInvalid = 120013
|
||||
ErrPlaceProxyBidCurrencyInvalid = 120014
|
||||
ErrPlaceProxyBidMaximumBidAmountMissing = 120016
|
||||
)
|
||||
|
||||
// PlaceProxyBid places a proxy bid for the buyer on a specific auction item.
|
||||
//
|
||||
// You must ensure the user agrees to the "Terms of use for Adult Only category"
|
||||
// (https://signin.ebay.com/ws/eBayISAPI.dll?AdultSignIn2) if he wishes to bid on on a adult-only item.
|
||||
//
|
||||
// eBay API docs: https://developer.ebay.com/api-docs/buy/offer/resources/bidding/methods/getBidding
|
||||
func (s *OfferService) PlaceProxyBid(ctx context.Context, itemID, marketplaceID, maxAmount, currency string, userConsentAdultOnlyItem bool, opts ...Opt) (ProxyBid, error) {
|
||||
u := fmt.Sprintf("buy/offer/v1_beta/bidding/%s/place_proxy_bid", itemID)
|
||||
opts = append(opts, OptBuyMarketplace(marketplaceID))
|
||||
pl := struct {
|
||||
MaxAmount struct {
|
||||
Currency string `json:"currency"`
|
||||
Value string `json:"value"`
|
||||
} `json:"maxAmount"`
|
||||
UserConsent struct {
|
||||
AdultOnlyItem bool `json:"adultOnlyItem"`
|
||||
} `json:"userConsent"`
|
||||
}{
|
||||
MaxAmount: struct {
|
||||
Currency string `json:"currency"`
|
||||
Value string `json:"value"`
|
||||
}{currency, maxAmount},
|
||||
UserConsent: struct {
|
||||
AdultOnlyItem bool `json:"adultOnlyItem"`
|
||||
}{userConsentAdultOnlyItem},
|
||||
}
|
||||
req, err := s.client.NewRequest(http.MethodPost, u, &pl, opts...)
|
||||
if err != nil {
|
||||
return ProxyBid{}, err
|
||||
}
|
||||
var p ProxyBid
|
||||
return p, s.client.Do(ctx, req, &p)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package ebay_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@@ -13,3 +15,17 @@ func TestOptBuyMarketplace(t *testing.T) {
|
||||
ebay.OptBuyMarketplace("EBAY_US")(r)
|
||||
assert.Equal(t, "EBAY_US", r.Header.Get("X-EBAY-C-MARKETPLACE-ID"))
|
||||
}
|
||||
|
||||
func TestGetBidding(t *testing.T) {
|
||||
client, mux, teardown := setup(t)
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/buy/offer/v1_beta/bidding/v1|202117468662|0", func(w http.ResponseWriter, r *http.Request) {
|
||||
marketplaceID := r.Header.Get("X-EBAY-C-MARKETPLACE-ID")
|
||||
fmt.Fprintf(w, `{"itemId": "%s"}`, marketplaceID)
|
||||
})
|
||||
|
||||
bidding, err := client.Buy.Offer.GetBidding(context.Background(), "v1|202117468662|0", ebay.BuyMarketplaceUSA)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ebay.BuyMarketplaceUSA, bidding.ItemID)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -45,8 +44,9 @@ func TestAuction(t *testing.T) {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
// Manually create an auction in the sandbox and copy/paste the url:
|
||||
const auctionURL = "https://www.sandbox.ebay.com/itm/110439278158"
|
||||
// Manually create an auction in the sandbox and copy/paste the url.
|
||||
// Auctions can't be created using the rest api (yet?).
|
||||
const auctionURL = "https://www.sandbox.ebay.com/itm/110440008951"
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestAuction(t *testing.T) {
|
||||
t.Fatalf("item %s is not an auction. BuyingOptions are: %+v", it.ItemID, it.BuyingOptions)
|
||||
}
|
||||
if time.Now().UTC().After(it.ItemEndDate) {
|
||||
// t.Fatalf("item %s end date has been reached. ItemEndDate is: %s", it.ItemID, it.ItemEndDate.String())
|
||||
t.Fatalf("item %s end date has been reached. ItemEndDate is: %s", it.ItemID, it.ItemEndDate.String())
|
||||
}
|
||||
t.Logf("item %s UniqueBidderCount:%d minimumBidPrice: %+v currentPriceToBid: %+v\n", it.ItemID, it.UniqueBidderCount, it.MinimumPriceToBid, it.CurrentBidPrice)
|
||||
|
||||
@@ -92,7 +92,6 @@ func TestAuction(t *testing.T) {
|
||||
}
|
||||
state := url.QueryEscape(string(b))
|
||||
authCodeC := make(chan string)
|
||||
var expiresIn time.Duration
|
||||
http.HandleFunc("/accept", func(rw http.ResponseWriter, r *http.Request) {
|
||||
actualState, err := url.QueryUnescape(r.URL.Query().Get("state"))
|
||||
if err != nil {
|
||||
@@ -104,12 +103,6 @@ func TestAuction(t *testing.T) {
|
||||
return
|
||||
}
|
||||
code := r.URL.Query().Get("code")
|
||||
expiresInSeconds, err := strconv.Atoi(r.URL.Query().Get("expires_in"))
|
||||
if err != nil {
|
||||
http.Error(rw, fmt.Sprintf("invalid expires_in: %+v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
expiresIn = time.Second * time.Duration(expiresInSeconds)
|
||||
authCodeC <- code
|
||||
t.Logf("The authorization code is %s.\n", code)
|
||||
t.Logf("The authorization code will expire in %s seconds.\n", r.URL.Query().Get("expires_in"))
|
||||
@@ -146,18 +139,13 @@ func TestAuction(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Force token regen.
|
||||
|
||||
// tok.Expiry = time.Now().Add(-time.Hour * 24) // not working?
|
||||
|
||||
fmt.Printf("Sleeping %v so token expires\n", expiresIn)
|
||||
time.Sleep(expiresIn + time.Second*5)
|
||||
|
||||
client = ebay.NewSandboxClient(oauth2.NewClient(ctx, tokensource.New(oauthConf.TokenSource(ctx, tok))))
|
||||
|
||||
bidding, err := client.Buy.Offer.GetBidding(ctx, it.ItemID, ebay.BuyMarketplaceUSA)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", err)
|
||||
_, err = client.Buy.Offer.GetBidding(ctx, it.ItemID, ebay.BuyMarketplaceUSA)
|
||||
if !ebay.IsError(err, ebay.ErrGetBiddingNoBiddingActivity) {
|
||||
t.Logf("Expected ErrNoBiddingActivity, got %+v.", err)
|
||||
}
|
||||
t.Logf("item %s bidding: %+v\n", it.ItemID, bidding)
|
||||
|
||||
// err := client.Buy.Offer.PlaceProxyBid(ctx)
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ func New(base oauth2.TokenSource) *TokenSource {
|
||||
func (ts *TokenSource) Token() (*oauth2.Token, error) {
|
||||
t, err := ts.base.Token()
|
||||
if t != nil {
|
||||
print("new token: " + t.AccessToken + "\n") // TODO remove
|
||||
t.TokenType = "Bearer"
|
||||
}
|
||||
return t, err
|
||||
|
||||
Reference in New Issue
Block a user