package paypal import ( "context" "errors" "fmt" "math/rand" "net/http" "strings" "testing" "time" "github.com/stretchr/testify/assert" ) 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 || (!c.tokenExpiresAt.IsZero() && time.Until(c.tokenExpiresAt) < 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()) // 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) } }