Skip to content

Commit 1bb626d

Browse files
akshayDev17Akshay Prabhakant
and
Akshay Prabhakant
authored
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>
1 parent f0575ee commit 1bb626d

File tree

3 files changed

+155
-11
lines changed

3 files changed

+155
-11
lines changed

client.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -138,25 +138,27 @@ func (c *Client) Send(req *http.Request, v interface{}) error {
138138
// making the main request
139139
// client.Token will be updated when changed
140140
func (c *Client) SendWithAuth(req *http.Request, v interface{}) error {
141-
c.Lock()
141+
// c.Lock()
142+
c.mu.Lock()
142143
// Note: Here we do not want to `defer c.Unlock()` because we need `c.Send(...)`
143144
// to happen outside of the locked section.
144145

145146
if c.Token != nil {
146147
if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn {
147148
// c.Token will be updated in GetAccessToken call
148149
if _, err := c.GetAccessToken(req.Context()); err != nil {
149-
c.Unlock()
150+
// c.Unlock()
151+
c.mu.Unlock()
150152
return err
151153
}
152154
}
153155

154156
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
155157
}
156-
157158
// Unlock the client mutex before sending the request, this allows multiple requests
158159
// to be in progress at the same time.
159-
c.Unlock()
160+
// c.Unlock()
161+
c.mu.Unlock()
160162
return c.Send(req, v)
161163
}
162164

client_test.go

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package paypal
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"math/rand"
8+
"net/http"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
// testClientID, testSecret imported from order_test.go
17+
18+
// All test values are defined here
19+
// var testClientID = "AXy9orp-CDaHhBZ9C78QHW2BKZpACgroqo85_NIOa9mIfJ9QnSVKzY-X_rivR_fTUUr6aLjcJsj6sDur"
20+
// var testSecret = "EBoIiUSkCKeSk49hHSgTem1qnjzzJgRQHDEHvGpzlLEf_nIoJd91xu8rPOBDCdR_UYNKVxJE-UgS2iCw"
21+
var testUserID = "https://www.paypal.com/webapps/auth/identity/user/VBqgHcgZwb1PBs69ybjjXfIW86_Hr93aBvF_Rgbh2II"
22+
var testCardID = "CARD-54E6956910402550WKGRL6EA"
23+
24+
var testProductId = "" // will be fetched in func TestProduct(t *testing.T)
25+
var testBillingPlan = "" // will be fetched in func TestSubscriptionPlans(t *testing.T)
26+
27+
const alphabet = "abcedfghijklmnopqrstuvwxyz"
28+
29+
func RandomString(n int) string {
30+
var sb strings.Builder
31+
k := len(alphabet)
32+
33+
for i := 0; i < n; i++ {
34+
c := alphabet[rand.Intn(k)]
35+
sb.WriteByte(c)
36+
}
37+
return sb.String()
38+
}
39+
40+
func createRandomProduct(t *testing.T) Product {
41+
//create a product
42+
productData := Product{
43+
Name: RandomString(10),
44+
Description: RandomString(100),
45+
Category: ProductCategorySoftware,
46+
Type: ProductTypeService,
47+
ImageUrl: "https://example.com/image.png",
48+
HomeUrl: "https://example.com",
49+
}
50+
return productData
51+
}
52+
53+
// this is a simple copy of the SendWithAuth method, used to
54+
// test the Lock and Unlock methods of the private mutex field
55+
// of Client structure.
56+
func (c *Client) sendWithAuth(req *http.Request, v interface{}) error {
57+
// c.Lock()
58+
c.mu.Lock()
59+
// Note: Here we do not want to `defer c.Unlock()` because we need `c.Send(...)`
60+
// to happen outside of the locked section.
61+
62+
if c.mu.TryLock() {
63+
// if the code is able to acquire a lock
64+
// despite the mutex of c being locked, throw an error
65+
err := errors.New("TryLock succeeded inside sendWithAuth with mutex locked")
66+
return err
67+
}
68+
69+
if c.Token != nil {
70+
if !c.tokenExpiresAt.IsZero() && c.tokenExpiresAt.Sub(time.Now()) < RequestNewTokenBeforeExpiresIn {
71+
// c.Token will be updated in GetAccessToken call
72+
if _, err := c.GetAccessToken(req.Context()); err != nil {
73+
// c.Unlock()
74+
c.mu.Unlock()
75+
return err
76+
}
77+
}
78+
79+
req.Header.Set("Authorization", "Bearer "+c.Token.Token)
80+
}
81+
// Unlock the client mutex before sending the request, this allows multiple requests
82+
// to be in progress at the same time.
83+
// c.Unlock()
84+
c.mu.Unlock()
85+
86+
if !c.mu.TryLock() {
87+
// if the code is unable to acquire a lock
88+
// despite the mutex of c being unlocked, throw an error
89+
err := errors.New("TryLock failed inside sendWithAuth with mutex unlocked")
90+
return err
91+
}
92+
c.mu.Unlock() // undo changes from the previous TryLock
93+
94+
return c.Send(req, v)
95+
}
96+
97+
// this method is used to invoke the sendWithAuth method, which will then check
98+
// operationally the privated mutex field of Client structure.
99+
func (c *Client) createProduct(ctx context.Context, product Product) (*CreateProductResponse, error) {
100+
req, err := c.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.APIBase, "/v1/catalogs/products"), product)
101+
response := &CreateProductResponse{}
102+
if err != nil {
103+
return response, err
104+
}
105+
err = c.sendWithAuth(req, response)
106+
return response, err
107+
}
108+
109+
func TestClientMutex(t *testing.T) {
110+
c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
111+
c.GetAccessToken(context.Background())
112+
113+
// Basic Testing of the private mutex field
114+
c.mu.Lock()
115+
if c.mu.TryLock() {
116+
t.Fatalf("TryLock succeeded with mutex locked")
117+
}
118+
c.mu.Unlock()
119+
if !c.mu.TryLock() {
120+
t.Fatalf("TryLock failed with mutex unlocked")
121+
}
122+
c.mu.Unlock() // undo changes from the previous TryLock
123+
124+
// Operational testing of the private mutex field
125+
n_iter := 2
126+
127+
errs := make(chan error)
128+
129+
for i := 0; i < n_iter; i++ {
130+
go func() {
131+
_, err := c.createProduct(context.Background(), createRandomProduct(t))
132+
errs <- err
133+
}()
134+
}
135+
136+
for i := 0; i < n_iter; i++ {
137+
err := <-errs
138+
assert.Equal(t, nil, err)
139+
}
140+
141+
}

types.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,8 @@ type (
405405

406406
// Client represents a Paypal REST API Client
407407
Client struct {
408-
sync.Mutex
408+
// sync.Mutex
409+
mu sync.Mutex
409410
Client *http.Client
410411
ClientID string
411412
Secret string
@@ -495,12 +496,12 @@ type (
495496

496497
// ErrorResponseDetail struct
497498
ErrorResponseDetail struct {
498-
Field string `json:"field"`
499-
Issue string `json:"issue"`
500-
Name string `json:"name"`
501-
Message string `json:"message"`
502-
Description string `json:"description"`
503-
Links []Link `json:"link"`
499+
Field string `json:"field"`
500+
Issue string `json:"issue"`
501+
Name string `json:"name"`
502+
Message string `json:"message"`
503+
Description string `json:"description"`
504+
Links []Link `json:"link"`
504505
}
505506

506507
// ErrorResponse https://developer.paypal.com/docs/api/errors/

0 commit comments

Comments
 (0)