Skip to content

Commit

Permalink
Feature/private mutex (#248)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
akshayDev17 and Akshay Prabhakant authored Oct 20, 2022
1 parent f0575ee commit 1bb626d
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 11 deletions.
10 changes: 6 additions & 4 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,25 +138,27 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
// making the main request
// client.Token will be updated when changed
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(...)`
// to happen outside of the locked section.

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.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.Unlock()
c.mu.Unlock()
return c.Send(req, v)
}

Expand Down
141 changes: 141 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

}
15 changes: 8 additions & 7 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@ type (

// Client represents a Paypal REST API Client
Client struct {
sync.Mutex
// sync.Mutex
mu sync.Mutex
Client *http.Client
ClientID string
Secret string
Expand Down Expand Up @@ -495,12 +496,12 @@ type (

// ErrorResponseDetail struct
ErrorResponseDetail struct {
Field string `json:"field"`
Issue string `json:"issue"`
Name string `json:"name"`
Message string `json:"message"`
Description string `json:"description"`
Links []Link `json:"link"`
Field string `json:"field"`
Issue string `json:"issue"`
Name string `json:"name"`
Message string `json:"message"`
Description string `json:"description"`
Links []Link `json:"link"`
}

// ErrorResponse https://developer.paypal.com/docs/api/errors/
Expand Down

0 comments on commit 1bb626d

Please sign in to comment.