From b8f2c8c5730e0190c4f0409c588585c5a0c61943 Mon Sep 17 00:00:00 2001 From: Alex Pliutau Date: Mon, 27 May 2024 12:36:25 +0200 Subject: [PATCH] Use github actions --- .github/workflows/lint-test.yaml | 40 +++ .travis.yml | 9 - README.md | 4 +- client_test.go | 22 -- example_test.go | 59 ---- products.go | 10 +- subscription.go | 6 +- subscription_plan.go | 20 +- unit_test.go | 489 ------------------------------- webhooks.go | 2 +- 10 files changed, 60 insertions(+), 601 deletions(-) create mode 100644 .github/workflows/lint-test.yaml delete mode 100644 .travis.yml delete mode 100644 example_test.go diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint-test.yaml new file mode 100644 index 0000000..ead7ade --- /dev/null +++ b/.github/workflows/lint-test.yaml @@ -0,0 +1,40 @@ +name: Lint and Test + +on: push + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21.x" + - name: Install dependencies + run: go get . + - name: Install linters + run: | + go install honnef.co/go/tools/cmd/staticcheck@latest + go install mvdan.cc/unparam@latest + - name: go vet + run: go vet ${{ inputs.path }} + - name: staticcheck + run: staticcheck ${{ inputs.path }} + - name: unparam + run: unparam ${{ inputs.path }} + + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21.x" + - name: Install dependencies + run: go get . + - name: Run Tests + run: go test -v -race ./... diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index efcc81c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: go -go: - - 1.13 - - 1.14 - - 1.15 -install: - - export PATH=$PATH:$HOME/gopath/bin -script: - - go test -v -race diff --git a/README.md b/README.md index e83b789..f17f7a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -[![Go Report Card](https://goreportcard.com/badge/plutov/paypal)](https://goreportcard.com/report/plutov/paypal) -[![Build Status](https://travis-ci.org/plutov/paypal.svg?branch=master)](https://travis-ci.org/plutov/paypal) -[![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/plutov/paypal) +[Docs](https://pkg.go.dev/github.com/plutov/paypal) # Go client for PayPal REST API diff --git a/client_test.go b/client_test.go index 4810b83..d18fa4f 100644 --- a/client_test.go +++ b/client_test.go @@ -13,17 +13,6 @@ import ( "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 { @@ -106,17 +95,6 @@ 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 diff --git a/example_test.go b/example_test.go deleted file mode 100644 index 99eda77..0000000 --- a/example_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package paypal_test - -import ( - "context" - - "github.com/plutov/paypal/v4" -) - -func Example() { - // Initialize client - c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox) - if err != nil { - panic(err) - } - - // Retrieve access token - _, err = c.GetAccessToken(context.Background()) - if err != nil { - panic(err) - } -} - -func ExampleClient_CreatePayout_Venmo() { - // Initialize client - c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox) - if err != nil { - panic(err) - } - - // Retrieve access token - _, err = c.GetAccessToken(context.Background()) - if err != nil { - panic(err) - } - - // Set payout item with Venmo wallet - payout := paypal.Payout{ - SenderBatchHeader: &paypal.SenderBatchHeader{ - SenderBatchID: "Payouts_2018_100007", - EmailSubject: "You have a payout!", - EmailMessage: "You have received a payout! Thanks for using our service!", - }, - Items: []paypal.PayoutItem{ - { - RecipientType: "EMAIL", - RecipientWallet: paypal.VenmoRecipientWallet, - Receiver: "receiver@example.com", - Amount: &paypal.AmountPayout{ - Value: "9.87", - Currency: "USD", - }, - Note: "Thanks for your patronage!", - SenderItemID: "201403140001", - }, - }, - } - - c.CreatePayout(context.Background(), payout) -} diff --git a/products.go b/products.go index cd45f66..30585ea 100644 --- a/products.go +++ b/products.go @@ -33,27 +33,27 @@ type ( } ) -func (self *Product) GetUpdatePatch() []Patch { +func (p *Product) GetUpdatePatch() []Patch { return []Patch{ { Operation: "replace", Path: "/description", - Value: self.Description, + Value: p.Description, }, { Operation: "replace", Path: "/category", - Value: self.Category, + Value: p.Category, }, { Operation: "replace", Path: "/image_url", - Value: self.ImageUrl, + Value: p.ImageUrl, }, { Operation: "replace", Path: "/home_url", - Value: self.HomeUrl, + Value: p.HomeUrl, }, } } diff --git a/subscription.go b/subscription.go index ab7f002..05e13f0 100644 --- a/subscription.go +++ b/subscription.go @@ -77,7 +77,7 @@ type ( // https://developer.paypal.com/docs/api/subscriptions/v1/#definition-plan_override PlanOverride struct { - BillingCycles []BillingCycleOverride `json:"billing_cycles,omitempty"` + BillingCycles []BillingCycleOverride `json:"billing_cycles,omitempty"` PaymentPreferences *PaymentPreferencesOverride `json:"payment_preferences,omitempty"` Taxes *TaxesOverride `json:"taxes,omitempty"` } @@ -104,12 +104,12 @@ type ( } ) -func (self *Subscription) GetUpdatePatch() []Patch { +func (p *Subscription) GetUpdatePatch() []Patch { result := []Patch{ { Operation: "replace", Path: "/billing_info/outstanding_balance", - Value: self.BillingInfo.OutstandingBalance, + Value: p.BillingInfo.OutstandingBalance, }, } return result diff --git a/subscription_plan.go b/subscription_plan.go index ffa461f..953697b 100644 --- a/subscription_plan.go +++ b/subscription_plan.go @@ -82,29 +82,29 @@ type ( } ) -func (self *SubscriptionPlan) GetUpdatePatch() []Patch { +func (p *SubscriptionPlan) GetUpdatePatch() []Patch { result := []Patch{ { Operation: "replace", Path: "/description", - Value: self.Description, + Value: p.Description, }, } - if self.Taxes != nil { + if p.Taxes != nil { result = append(result, Patch{ Operation: "replace", Path: "/taxes/percentage", - Value: self.Taxes.Percentage, + Value: p.Taxes.Percentage, }) } - if self.PaymentPreferences != nil { - if self.PaymentPreferences.SetupFee != nil { + if p.PaymentPreferences != nil { + if p.PaymentPreferences.SetupFee != nil { result = append(result, Patch{ Operation: "replace", Path: "/payment_preferences/setup_fee", - Value: self.PaymentPreferences.SetupFee, + Value: p.PaymentPreferences.SetupFee, }, ) } @@ -112,17 +112,17 @@ func (self *SubscriptionPlan) GetUpdatePatch() []Patch { result = append(result, []Patch{{ Operation: "replace", Path: "/payment_preferences/auto_bill_outstanding", - Value: self.PaymentPreferences.AutoBillOutstanding, + Value: p.PaymentPreferences.AutoBillOutstanding, }, { Operation: "replace", Path: "/payment_preferences/payment_failure_threshold", - Value: self.PaymentPreferences.PaymentFailureThreshold, + Value: p.PaymentPreferences.PaymentFailureThreshold, }, { Operation: "replace", Path: "/payment_preferences/setup_fee_failure_action", - Value: self.PaymentPreferences.SetupFeeFailureAction, + Value: p.PaymentPreferences.SetupFeeFailureAction, }}...) } diff --git a/unit_test.go b/unit_test.go index c261af8..3f48fd2 100644 --- a/unit_test.go +++ b/unit_test.go @@ -1,21 +1,10 @@ package paypal import ( - "context" "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" "testing" ) -var testBillingAgreementID = "BillingAgreementID" - -type webprofileTestServer struct { - t *testing.T -} - func TestNewClient(t *testing.T) { c, err := NewClient("", "", "") if err == nil { @@ -482,481 +471,3 @@ func TestTypePaymentPatchMarshal(t *testing.T) { t.Errorf("PaymentPatch response2 is incorrect,\n Given: %+v\n Expected: %+v", string(response2), string(p2expectedresponse)) } } - -// ServeHTTP implements http.Handler -func (ts *webprofileTestServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ts.t.Log(r.RequestURI) - if r.RequestURI == "/v1/payment-experience/web-profiles" { - if r.Method == "POST" { - ts.create(w, r) - } - if r.Method == "GET" { - ts.list(w, r) - } - } - if r.RequestURI == "/v1/payment-experience/web-profiles/XP-CP6S-W9DY-96H8-MVN2" { - if r.Method == "GET" { - ts.getvalid(w, r) - } - if r.Method == "PUT" { - ts.updatevalid(w, r) - } - if r.Method == "DELETE" { - ts.deletevalid(w, r) - } - } - if r.RequestURI == "/v1/payment-experience/web-profiles/foobar" { - if r.Method == "GET" { - ts.getinvalid(w, r) - } - if r.Method == "PUT" { - ts.updateinvalid(w, r) - } - if r.Method == "DELETE" { - ts.deleteinvalid(w, r) - } - } - if r.RequestURI == "/v1/billing-agreements/agreement-tokens" { - if r.Method == "POST" { - ts.createWithoutName(w, r) - } - } - if r.RequestURI == "/v1/billing-agreements/agreements" { - if r.Method == "POST" { - ts.createWithoutName(w, r) - } - } - if r.RequestURI == fmt.Sprintf("/v1/billing-agreements/agreements/%s/cancel", testBillingAgreementID) { - if r.Method == "POST" { - ts.deletevalid(w, r) - } - } -} - -func (ts *webprofileTestServer) create(w http.ResponseWriter, r *http.Request) { - var data map[string]interface{} - - body, err := io.ReadAll(r.Body) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - err = json.Unmarshal(body, &data) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - var raw map[string]string - - w.Header().Set("Content-Type", "application/json") - - if name, ok := data["name"]; !ok || name == "" { - raw = map[string]string{ - "name": "VALIDATION_ERROR", - "message": "should have name", - } - w.WriteHeader(http.StatusBadRequest) - } else { - raw = map[string]string{ - "id": "XP-CP6S-W9DY-96H8-MVN2", - } - w.WriteHeader(http.StatusCreated) - } - - res, _ := json.Marshal(raw) - w.Write(res) -} - -func (ts *webprofileTestServer) createWithoutName(w http.ResponseWriter, r *http.Request) { - var data map[string]interface{} - - body, err := io.ReadAll(r.Body) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - err = json.Unmarshal(body, &data) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - var raw map[string]string - - w.Header().Set("Content-Type", "application/json") - - raw = map[string]string{ - "id": "B-12345678901234567", - } - w.WriteHeader(http.StatusCreated) - - res, _ := json.Marshal(raw) - w.Write(res) -} - -func (ts *webprofileTestServer) updatevalid(w http.ResponseWriter, r *http.Request) { - var data map[string]interface{} - - body, err := io.ReadAll(r.Body) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - err = json.Unmarshal(body, &data) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - if ID, ok := data["id"]; !ok || ID != "XP-CP6S-W9DY-96H8-MVN2" { - raw := map[string]string{ - "name": "INVALID_RESOURCE_ID", - "message": "id invalid", - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - res, _ := json.Marshal(raw) - w.Write(res) - return - } - - if name, ok := data["name"]; !ok || name == "" { - raw := map[string]string{ - "name": "VALIDATION_ERROR", - "message": "should have name", - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - res, _ := json.Marshal(raw) - w.Write(res) - return - } - - w.WriteHeader(http.StatusNoContent) - -} - -func (ts *webprofileTestServer) updateinvalid(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusNotFound) - - raw := map[string]interface{}{ - "name": "INVALID_RESOURCE_ID", - "message": "foobar not found", - } - - res, _ := json.Marshal(raw) - w.Write(res) -} - -func (ts *webprofileTestServer) getvalid(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - raw := map[string]interface{}{ - "id": "XP-CP6S-W9DY-96H8-MVN2", - "name": "YeowZa! T-Shirt Shop", - "presentation": map[string]interface{}{ - "brand_name": "YeowZa! Paypal", - "logo_image": "http://www.yeowza.com", - "locale_code": "US", - }, - - "input_fields": map[string]interface{}{ - "allow_note": true, - "no_shipping": 0, - "address_override": 1, - }, - - "flow_config": map[string]interface{}{ - "landing_page_type": "Billing", - "bank_txn_pending_url": "http://www.yeowza.com", - }, - } - - res, _ := json.Marshal(raw) - w.Write(res) -} - -func (ts *webprofileTestServer) getinvalid(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusNotFound) - - raw := map[string]interface{}{ - "name": "INVALID_RESOURCE_ID", - "message": "foobar not found", - } - - res, _ := json.Marshal(raw) - w.Write(res) -} - -func (ts *webprofileTestServer) list(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - raw := []interface{}{ - map[string]interface{}{ - "id": "XP-CP6S-W9DY-96H8-MVN2", - "name": "YeowZa! T-Shirt Shop", - }, - map[string]interface{}{ - "id": "XP-96H8-MVN2-CP6S-W9DY", - "name": "Shop T-Shirt YeowZa! ", - }, - } - - res, _ := json.Marshal(raw) - w.Write(res) -} - -func (ts *webprofileTestServer) deleteinvalid(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusNotFound) - - raw := map[string]interface{}{ - "name": "INVALID_RESOURCE_ID", - "message": "foobar not found", - } - - res, _ := json.Marshal(raw) - w.Write(res) -} - -func (ts *webprofileTestServer) deletevalid(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusNoContent) -} - -func TestCreateWebProfile_valid(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - wp := WebProfile{ - Name: "YeowZa! T-Shirt Shop", - Presentation: Presentation{ - BrandName: "YeowZa! Paypal", - LogoImage: "http://www.yeowza.com", - LocaleCode: "US", - }, - - InputFields: InputFields{ - AllowNote: true, - NoShipping: NoShippingDisplay, - AddressOverride: AddrOverrideFromCall, - }, - - FlowConfig: FlowConfig{ - LandingPageType: LandingPageTypeBilling, - BankTXNPendingURL: "http://www.yeowza.com", - }, - } - - res, err := c.CreateWebProfile(context.Background(), wp) - - if err != nil { - t.Fatal(err) - } - - if res.ID != "XP-CP6S-W9DY-96H8-MVN2" { - t.Fatalf("expecting response to have ID = `XP-CP6S-W9DY-96H8-MVN2` got `%s`", res.ID) - } -} - -func TestCreateWebProfile_invalid(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - wp := WebProfile{} - - _, err := c.CreateWebProfile(context.Background(), wp) - - if err == nil { - t.Fatalf("expecting an error got nil") - } -} - -func TestGetWebProfile_valid(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - res, err := c.GetWebProfile(context.Background(), "XP-CP6S-W9DY-96H8-MVN2") - - if err != nil { - t.Fatal(err) - } - - if res.ID != "XP-CP6S-W9DY-96H8-MVN2" { - t.Fatalf("expecting res.ID to have value = `XP-CP6S-W9DY-96H8-MVN2` but got `%s`", res.ID) - } - - if res.Name != "YeowZa! T-Shirt Shop" { - t.Fatalf("expecting res.Name to have value = `YeowZa! T-Shirt Shop` but got `%s`", res.Name) - } -} - -func TestGetWebProfile_invalid(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - _, err := c.GetWebProfile(context.Background(), "foobar") - - if err == nil { - t.Fatalf("expecting an error got nil") - } -} - -func TestGetWebProfiles(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - res, err := c.GetWebProfiles(context.Background()) - - if err != nil { - t.Fatal(err) - } - - if len(res) != 2 { - t.Fatalf("expecting two results got %d", len(res)) - } -} - -func TestSetWebProfile_valid(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - wp := WebProfile{ - ID: "XP-CP6S-W9DY-96H8-MVN2", - Name: "Shop T-Shirt YeowZa!", - } - - err := c.SetWebProfile(context.Background(), wp) - - if err != nil { - t.Fatal(err) - } - -} - -func TestSetWebProfile_invalid(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - wp := WebProfile{ - ID: "foobar", - } - - err := c.SetWebProfile(context.Background(), wp) - - if err == nil { - t.Fatal(err) - } - - wp = WebProfile{} - - err = c.SetWebProfile(context.Background(), wp) - - if err == nil { - t.Fatal(err) - } -} - -func TestDeleteWebProfile_valid(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - wp := WebProfile{ - ID: "XP-CP6S-W9DY-96H8-MVN2", - Name: "Shop T-Shirt YeowZa!", - } - - err := c.SetWebProfile(context.Background(), wp) - - if err != nil { - t.Fatal(err) - } - -} - -func TestDeleteWebProfile_invalid(t *testing.T) { - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - err := c.DeleteWebProfile(context.Background(), "foobar") - - if err == nil { - t.Fatal(err) - } - -} - -func TestCreateBillingAgreementToken(t *testing.T) { - - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - description := "name A" - - _, err := c.CreateBillingAgreementToken( - context.Background(), - &description, - &ShippingAddress{RecipientName: "Name", Type: "Type", Line1: "Line1", Line2: "Line2"}, - &Payer{PaymentMethod: "paypal"}, - &BillingPlan{ID: "id B", Name: "name B", Description: "description B", Type: "type B"}) - - if err != nil { - t.Fatal(err) - } - -} - -func TestCreateBillingAgreementFromToken(t *testing.T) { - - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - _, err := c.CreateBillingAgreementFromToken(context.Background(), "BillingAgreementToken") - - if err != nil { - t.Fatal(err) - } -} - -func TestCancelBillingAgreement(t *testing.T) { - - ts := httptest.NewServer(&webprofileTestServer{t: t}) - defer ts.Close() - - c, _ := NewClient("foo", "bar", ts.URL) - - err := c.CancelBillingAgreement(context.Background(), testBillingAgreementID) - - if err != nil { - t.Fatal(err) - } -} diff --git a/webhooks.go b/webhooks.go index b76c75f..b18ad1b 100644 --- a/webhooks.go +++ b/webhooks.go @@ -98,7 +98,7 @@ func (c *Client) VerifyWebhookSignature(ctx context.Context, httpReq *http.Reque if httpReq.Body != nil { bodyBytes, _ = io.ReadAll(httpReq.Body) } else { - return nil, errors.New("Cannot verify webhook for HTTP Request with empty body.") + return nil, errors.New("cannot verify webhook for HTTP Request with empty body") } // Restore the io.ReadCloser to its original state httpReq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))