Skip to content

Commit

Permalink
Merge pull request #6 from hostwithquantum/misc-fixes
Browse files Browse the repository at this point in the history
Misc fixes
  • Loading branch information
rwos committed Feb 13, 2024
2 parents 34d8d8d + 76f3fa0 commit e500d6e
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 32 deletions.
35 changes: 26 additions & 9 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import (
"io"
"net/http"
"strings"
"time"
)

const (
baseURL = "https://api.lexoffice.io"
baseURL = "https://api.lexoffice.io"
maxRateLimitTries = 3
)

// Config is to define the request data
Expand Down Expand Up @@ -66,15 +68,26 @@ func (c *Config) Send(path string, body io.Reader, method, contentType string) (
request.Header.Set("Content-Type", contentType)
request.Header.Set("Accept", "application/json")

var response *http.Response
// Send request & get response
response, err := c.client.Do(request)
if err != nil {
return nil, err
}

if isSuccessful(response) {
// Return data
return response, nil
for i := 1; ; i++ {
response, err = c.client.Do(request)
if err != nil {
return nil, err
}

if isSuccessful(response) {
// Return data
return response, nil
} else if hitRateLimit(response) {
if i > maxRateLimitTries {
break
}
// max 2 requests per second, so let's wait a bit and try again
time.Sleep(500 * time.Duration(i) * time.Millisecond)
} else {
break
}
}

// TODO(till): revisit parsing when we add more API endpoints
Expand All @@ -89,6 +102,10 @@ func isSuccessful(response *http.Response) bool {
return response.StatusCode < 400
}

func hitRateLimit(response *http.Response) bool {
return response.StatusCode == 429
}

func parseErrorResponse(response *http.Response) error {
var errorResp ErrorResponse
err := json.NewDecoder(response.Body).Decode(&errorResp)
Expand Down
36 changes: 36 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,42 @@ func TestErrorNoDetails(t *testing.T) {
})
}

func TestRateLimit(t *testing.T) {
rateLimitHits := 10
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if rateLimitHits > 0 {
rateLimitHits -= 1
w.WriteHeader(http.StatusTooManyRequests)
//nolint:errcheck
w.Write([]byte(`{
"status": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded"
}`))
} else {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{}`)) //nolint:errcheck
}
}))
defer server.Close()

lexOffice := golexoffice.NewConfig("token", nil)
lexOffice.SetBaseUrl(server.URL)

t.Run("retry until ok", func(t *testing.T) {
rateLimitHits = 2
_, err := lexOffice.Invoice("tralalala")
assert.NoError(t, err)
})

t.Run("retry until out of retries", func(t *testing.T) {
rateLimitHits = 10
_, err := lexOffice.Invoice("tralalala")
assert.Error(t, err)
assert.ErrorContains(t, err, "Rate limit exceeded")
})
}

func errorMock() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/v1/contacts/" {
Expand Down
30 changes: 11 additions & 19 deletions invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ package golexoffice
import (
"bytes"
"encoding/json"
"fmt"
"io"
)

// InvoiceBody is to define body data
Expand All @@ -35,7 +33,7 @@ type InvoiceBody struct {
TotalPrice InvoiceBodyTotalPrice `json:"totalPrice"`
TaxAmounts []InvoiceBodyTaxAmounts `json:"taxAmounts,omitempty"`
TaxConditions InvoiceBodyTaxConditions `json:"taxConditions"`
PaymentConditions *InvoiceBodyPaymentConditions `json:"paymentConditions,omitempty"`
PaymentConditions *InvoiceBodyPaymentConditions `json:"paymentConditions,omitempty"`
ShippingConditions InvoiceBodyShippingConditions `json:"shippingConditions"`
Title string `json:"title,omitempty"`
Introduction string `json:"introduction,omitempty"`
Expand All @@ -58,26 +56,26 @@ type InvoiceBodyLineItems struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Quantity float64 `json:"quantity,omitempty"`
Quantity interface{} `json:"quantity,omitempty"`
UnitName string `json:"unitName,omitempty"`
UnitPrice InvoiceBodyUnitPrice `json:"unitPrice,omitempty"`
DiscountPercentage int `json:"discountPercentage,omitempty"`
LineItemAmount float64 `json:"lineItemAmount,omitempty"`
DiscountPercentage interface{} `json:"discountPercentage,omitempty"`
LineItemAmount interface{} `json:"lineItemAmount,omitempty"`
}

type InvoiceBodyUnitPrice struct {
Currency string `json:"currency"`
NetAmount float64 `json:"netAmount,omitempty"`
GrossAmount float64 `json:"grossAmount,omitempty"`
TaxRatePercentage int `json:"taxRatePercentage"`
Currency string `json:"currency"`
NetAmount interface{} `json:"netAmount,omitempty"`
GrossAmount interface{} `json:"grossAmount,omitempty"`
TaxRatePercentage int `json:"taxRatePercentage"`
}

type InvoiceBodyTotalPrice struct {
Currency string `json:"currency"`
TotalNetAmount float64 `json:"totalNetAmount,omitempty"`
TotalGrossAmount float64 `json:"totalGrossAmount,omitempty"`
TotalNetAmount interface{} `json:"totalNetAmount,omitempty"`
TotalGrossAmount interface{} `json:"totalGrossAmount,omitempty"`
TaxRatePercentage interface{} `json:"taxRatePercentage,omitempty"`
TotalTaxAmount float64 `json:"totalTaxAmount,omitempty"`
TotalTaxAmount interface{} `json:"totalTaxAmount,omitempty"`
TotalDiscountAbsolute interface{} `json:"totalDiscountAbsolute,omitempty"`
TotalDiscountPercentage interface{} `json:"totalDiscountPercentage,omitempty"`
}
Expand Down Expand Up @@ -134,12 +132,6 @@ func (c *Config) Invoice(id string) (InvoiceBody, error) {
// Close request
defer response.Body.Close()

read, err := io.ReadAll(response.Body)
if err != nil {
return InvoiceBody{}, err
}
fmt.Println(string(read))

// Decode data
var decode InvoiceBody

Expand Down
78 changes: 74 additions & 4 deletions invoices_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,86 @@
package golexoffice_test

import (
"encoding/json"
"testing"
"encoding/json"

"github.com/hostwithquantum/golexoffice"
"github.com/stretchr/testify/assert"
)

func TestEmptyInvoiceOnlyHasRequiredProperties(t *testing.T) {
body := golexoffice.InvoiceBody{}
body := golexoffice.InvoiceBody{}
encoded, err := json.Marshal(body)
assert.NoError(t, err)
assert.Equal(t, `{"voucherDate":"","address":{},"lineItems":null,"totalPrice":{"currency":""},"taxConditions":{"taxType":""},"shippingConditions":{"shippingType":""}}`, string(encoded))
assert.NoError(t, err)
assert.JSONEq(t, `
{
"voucherDate": "",
"address": {},
"lineItems": null,
"totalPrice": {
"currency": ""
},
"taxConditions": {
"taxType": ""
},
"shippingConditions": {
"shippingType": ""
}
}`, string(encoded))
}

func TestPriceOfZeroIsNotOmitted(t *testing.T) {
body := golexoffice.InvoiceBody{
TotalPrice: golexoffice.InvoiceBodyTotalPrice{
TotalGrossAmount: 0,
},
LineItems: []golexoffice.InvoiceBodyLineItems{
{
UnitPrice: golexoffice.InvoiceBodyUnitPrice{
NetAmount: 0.0,
},
},
{
UnitPrice: golexoffice.InvoiceBodyUnitPrice{
GrossAmount: 0.0,
},
}},
}
encoded, err := json.Marshal(body)
assert.NoError(t, err)
assert.JSONEq(t, `
{
"voucherDate": "",
"address": {},
"lineItems": [
{
"name": "",
"type": "",
"unitPrice": {
"currency": "",
"taxRatePercentage": 0,
"netAmount": 0
}
},
{
"name": "",
"type": "",
"unitPrice": {
"currency": "",
"taxRatePercentage": 0,
"grossAmount": 0
}
}
],
"totalPrice": {
"currency": "",
"totalGrossAmount": 0
},
"taxConditions": {
"taxType": ""
},
"shippingConditions": {
"shippingType": ""
}
}`, string(encoded))
}

0 comments on commit e500d6e

Please sign in to comment.