Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cmd/pricer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/mysteriumnetwork/discovery/middleware"
"github.com/mysteriumnetwork/discovery/price"
"github.com/mysteriumnetwork/discovery/price/pricingbyservice"
"github.com/mysteriumnetwork/go-rest/apierror"
mlog "github.com/mysteriumnetwork/logger"
)

Expand All @@ -41,8 +40,8 @@ func main() {

r := gin.New()
r.Use(gin.Recovery())
r.Use(middleware.ErrorHandler)
r.Use(middleware.Logger)
r.Use(apierror.ErrorHandler)

rdb := redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: cfg.RedisAddress,
Expand Down
36 changes: 36 additions & 0 deletions middleware/apierror.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package middleware

import (
"encoding/json"
"errors"

"github.com/gin-gonic/gin"
"github.com/mysteriumnetwork/go-rest/apierror"
"github.com/rs/zerolog/log"
)

// ErrorHandler formats request errors unless the response has already been sent.
func ErrorHandler(c *gin.Context) {
c.Next()
if len(c.Errors) < 1 {
return
}
if c.Writer.Written() {
log.Err(c.Errors[0].Err).Msg("response already written, skipping error response")
return
}

err := c.Errors[0].Err
var apiErr *apierror.APIError
if !errors.As(err, &apiErr) {
apiErr = apierror.Internal(err.Error(), apierror.ErrCodeInternal)
}
apiErr.Path = c.Request.URL.String()

blob, err := json.Marshal(apiErr)
if err != nil {
c.Data(500, apierror.ContentTypeV1, apierror.DefaultErrStatic)
return
}
c.Data(apiErr.Status, apierror.ContentTypeV1, blob)
}
17 changes: 15 additions & 2 deletions price/api_by_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package price

import (
"context"
"encoding/json"
"net/http"
"time"

Expand All @@ -15,6 +16,7 @@ import (

const (
errCodeParsingJson = "err_parsing_config"
errCodeMarshalJson = "err_marshal_prices"

errCodeNoConfig = "err_no_config"
errCodeUpdateConfig = "err_update_config"
Expand All @@ -23,13 +25,17 @@ const (
)

type APIByService struct {
pricer *pricingbyservice.PriceGetter
pricer latestPricer
cfger pricingbyservice.ConfigProvider
redis redis.UniversalClient

ac authCheck
}

type latestPricer interface {
GetPrices() pricingbyservice.LatestPrices
}

type authCheck interface {
JWTAuthorized() func(*gin.Context)
}
Expand All @@ -51,7 +57,14 @@ func NewAPIByService(redis redis.UniversalClient, pricer *pricingbyservice.Price
// @Router /prices [get]
// @Tags prices
func (a *APIByService) LatestPrices(c *gin.Context) {
c.JSON(200, a.pricer.GetPrices())
blob, err := json.Marshal(a.pricer.GetPrices())
if err != nil {
log.Err(err).Msg("Failed to marshal latest prices")
c.Error(apierror.Internal(err.Error(), errCodeMarshalJson))
return
}

c.Data(http.StatusOK, gin.MIMEJSON, blob)
}

// GetConfig returns the base pricing config
Expand Down
58 changes: 58 additions & 0 deletions price/api_by_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package price

import (
"math"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/gin-gonic/gin"

"github.com/mysteriumnetwork/discovery/middleware"
"github.com/mysteriumnetwork/discovery/price/pricingbyservice"
)

type staticLatestPricer struct {
prices pricingbyservice.LatestPrices
}

func (s staticLatestPricer) GetPrices() pricingbyservice.LatestPrices {
return s.prices
}

func TestLatestPricesReturnsErrorWhenJSONMarshalFails(t *testing.T) {
gin.SetMode(gin.TestMode)

api := &APIByService{
pricer: staticLatestPricer{
prices: pricingbyservice.LatestPrices{
Defaults: &pricingbyservice.PriceHistory{
Current: &pricingbyservice.PriceByType{
Residential: &pricingbyservice.PriceByServiceType{
Wireguard: pricingbyservice.Price{
PricePerHourHumanReadable: math.NaN(),
},
},
},
},
},
},
}

router := gin.New()
router.Use(middleware.ErrorHandler)
router.GET("/api/v4/prices", api.LatestPrices)

req := httptest.NewRequest(http.MethodGet, "/api/v4/prices", nil)
resp := httptest.NewRecorder()

router.ServeHTTP(resp, req)

if resp.Code != http.StatusInternalServerError {
t.Fatalf("status = %d, want %d", resp.Code, http.StatusInternalServerError)
}
if !strings.Contains(resp.Body.String(), errCodeMarshalJson) {
t.Fatalf("response body = %q, want error code %q", resp.Body.String(), errCodeMarshalJson)
}
}
12 changes: 12 additions & 0 deletions price/pricingbyservice/price_updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,18 @@ func TestPricer_isMystInSensibleLimit(t *testing.T) {
},
wantErr: true,
},
{
name: "accepts price below ten cents when inside configured bound",
fields: fields{
mystBound: Bound{
Min: 0.01,
Max: 3,
},
},
args: args{
price: 0.090547,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion sidecar/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func main() {
mrkt,
countryDemandIndexes,
time.Minute*5,
pricingbyservice.Bound{Min: 0.1, Max: 3.0},
pricingbyservice.Bound{Min: 0.01, Max: 3.0},
rdb,
)
if err != nil {
Expand Down
Loading