Feature/private mutex (#248)

* feat/mutexPrivate:init; trying whether changes get pushed

* feat/privatedMutex: privated the mutex as a field in Client struct, added tests for this private mutex field

* feature/privateMutex: commented the composition of sync.Mutex

* feature/privateMutex: resolved issues with user credentials

Co-authored-by: Akshay Prabhakant <akshayprabhakant@Akshays-MacBook-Pro.local>
This commit is contained in:
Akshay Prabhakant 2022-10-20 12:41:49 +05:30 committed by GitHub
parent f0575ee562
commit 1bb626d559
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 155 additions and 11 deletions

View File

@ -138,7 +138,8 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
// making the main request // making the main request
// client.Token will be updated when changed // client.Token will be updated when changed
func (c *Client) SendWithAuth(req *http.Request, v interface{}) error { func (c *Client) SendWithAuth(req *http.Request, v interface{}) error {
c.Lock() // c.Lock()
c.mu.Lock()
// Note: Here we do not want to `defer c.Unlock()` because we need `c.Send(...)` // Note: Here we do not want to `defer c.Unlock()` because we need `c.Send(...)`
// to happen outside of the locked section. // to happen outside of the locked section.
@ -146,17 +147,18 @@ func (c *Client) SendWithAuth(req *http.Request, v interface{}) error {
if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn { if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn {
// c.Token will be updated in GetAccessToken call // c.Token will be updated in GetAccessToken call
if _, err := c.GetAccessToken(req.Context()); err != nil { if _, err := c.GetAccessToken(req.Context()); err != nil {
c.Unlock() // c.Unlock()
c.mu.Unlock()
return err return err
} }
} }
req.Header.Set("Authorization", "Bearer "+c.Token.Token) req.Header.Set("Authorization", "Bearer "+c.Token.Token)
} }
// Unlock the client mutex before sending the request, this allows multiple requests // Unlock the client mutex before sending the request, this allows multiple requests
// to be in progress at the same time. // to be in progress at the same time.
c.Unlock() // c.Unlock()
c.mu.Unlock()
return c.Send(req, v) return c.Send(req, v)
} }

141
client_test.go Normal file
View File

@ -0,0 +1,141 @@
package paypal
import (
"context"
"errors"
"fmt"
"math/rand"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// testClientID, testSecret imported from order_test.go
// All test values are defined here
// var testClientID = "AXy9orp-CDaHhBZ9C78QHW2BKZpACgroqo85_NIOa9mIfJ9QnSVKzY-X_rivR_fTUUr6aLjcJsj6sDur"
// var testSecret = "EBoIiUSkCKeSk49hHSgTem1qnjzzJgRQHDEHvGpzlLEf_nIoJd91xu8rPOBDCdR_UYNKVxJE-UgS2iCw"
var testUserID = "https://www.paypal.com/webapps/auth/identity/user/VBqgHcgZwb1PBs69ybjjXfIW86_Hr93aBvF_Rgbh2II"
var testCardID = "CARD-54E6956910402550WKGRL6EA"
var testProductId = "" // will be fetched in func TestProduct(t *testing.T)
var testBillingPlan = "" // will be fetched in func TestSubscriptionPlans(t *testing.T)
const alphabet = "abcedfghijklmnopqrstuvwxyz"
func RandomString(n int) string {
var sb strings.Builder
k := len(alphabet)
for i := 0; i < n; i++ {
c := alphabet[rand.Intn(k)]
sb.WriteByte(c)
}
return sb.String()
}
func createRandomProduct(t *testing.T) Product {
//create a product
productData := Product{
Name: RandomString(10),
Description: RandomString(100),
Category: ProductCategorySoftware,
Type: ProductTypeService,
ImageUrl: "https://example.com/image.png",
HomeUrl: "https://example.com",
}
return productData
}
// this is a simple copy of the SendWithAuth method, used to
// test the Lock and Unlock methods of the private mutex field
// of Client structure.
func (c *Client) sendWithAuth(req *http.Request, v interface{}) error {
// c.Lock()
c.mu.Lock()
// Note: Here we do not want to `defer c.Unlock()` because we need `c.Send(...)`
// to happen outside of the locked section.
if c.mu.TryLock() {
// if the code is able to acquire a lock
// despite the mutex of c being locked, throw an error
err := errors.New("TryLock succeeded inside sendWithAuth with mutex locked")
return err
}
if c.Token != nil {
if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn {
// c.Token will be updated in GetAccessToken call
if _, err := c.GetAccessToken(req.Context()); err != nil {
// c.Unlock()
c.mu.Unlock()
return err
}
}
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
}
// Unlock the client mutex before sending the request, this allows multiple requests
// to be in progress at the same time.
// c.Unlock()
c.mu.Unlock()
if !c.mu.TryLock() {
// if the code is unable to acquire a lock
// despite the mutex of c being unlocked, throw an error
err := errors.New("TryLock failed inside sendWithAuth with mutex unlocked")
return err
}
c.mu.Unlock() // undo changes from the previous TryLock
return c.Send(req, v)
}
// this method is used to invoke the sendWithAuth method, which will then check
// operationally the privated mutex field of Client structure.
func (c *Client) createProduct(ctx context.Context, product Product) (*CreateProductResponse, error) {
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/catalogs/products"), product)
response := &CreateProductResponse{}
if err != nil {
return response, err
}
err = c.sendWithAuth(req, response)
return response, err
}
func TestClientMutex(t *testing.T) {
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken(context.Background())
// Basic Testing of the private mutex field
c.mu.Lock()
if c.mu.TryLock() {
t.Fatalf("TryLock succeeded with mutex locked")
}
c.mu.Unlock()
if !c.mu.TryLock() {
t.Fatalf("TryLock failed with mutex unlocked")
}
c.mu.Unlock() // undo changes from the previous TryLock
// Operational testing of the private mutex field
n_iter := 2
errs := make(chan error)
for i := 0; i < n_iter; i++ {
go func() {
_, err := c.createProduct(context.Background(), createRandomProduct(t))
errs <- err
}()
}
for i := 0; i < n_iter; i++ {
err := <-errs
assert.Equal(t, nil, err)
}
}

View File

@ -405,7 +405,8 @@ type (
// Client represents a Paypal REST API Client // Client represents a Paypal REST API Client
Client struct { Client struct {
sync.Mutex // sync.Mutex
mu sync.Mutex
Client *http.Client Client *http.Client
ClientID string ClientID string
Secret string Secret string
@ -495,12 +496,12 @@ type (
// ErrorResponseDetail struct // ErrorResponseDetail struct
ErrorResponseDetail struct { ErrorResponseDetail struct {
Field string `json:"field"` Field string `json:"field"`
Issue string `json:"issue"` Issue string `json:"issue"`
Name string `json:"name"` Name string `json:"name"`
Message string `json:"message"` Message string `json:"message"`
Description string `json:"description"` Description string `json:"description"`
Links []Link `json:"link"` Links []Link `json:"link"`
} }
// ErrorResponse https://developer.paypal.com/docs/api/errors/ // ErrorResponse https://developer.paypal.com/docs/api/errors/