Skip to content

Commit

Permalink
feat(SPV-1106): add errors and fix tests for tips and webhook
Browse files Browse the repository at this point in the history
  • Loading branch information
dzolt-4chain committed Oct 24, 2024
1 parent ec9d1f9 commit 7b50232
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 33 deletions.
23 changes: 23 additions & 0 deletions bhserrors/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,26 @@ var ErrHeaderStopHeightNotFound = BHSError{Message: "Could not find stop height

// ErrGetTips is when it fails to get tips
var ErrGetTips = BHSError{Message: "Failed to get tips", StatusCode: 400, Code: "ErrGetTips"}

// ////////////////////////////////// WEBHOOK ERRORS

// ErrUrlBodyRequired is when url is not provided in body
var ErrUrlBodyRequired = BHSError{Message: "URL is required", StatusCode: 400, Code: "ErrUrlBodyRequired"}

Check failure on line 87 in bhserrors/definitions.go

View workflow job for this annotation

GitHub Actions / on-push / Lint

var-naming: var ErrUrlBodyRequired should be ErrURLBodyRequired (revive)

// ErrUrlParamRequired is when url is not provided in param
var ErrUrlParamRequired = BHSError{Message: "URL is required", StatusCode: 400, Code: "ErrUrlParamRequired"}

Check failure on line 90 in bhserrors/definitions.go

View workflow job for this annotation

GitHub Actions / on-push / Lint

var-naming: var ErrUrlParamRequired should be ErrURLParamRequired (revive)

// ErrCreateWebhook is when it fails to create a webhook
var ErrCreateWebhook = BHSError{Message: "Failed to create a webhook", StatusCode: 400, Code: "ErrCreateWebhook"}

// ErrWebhookNotFound is when webhook was not found
var ErrWebhookNotFound = BHSError{Message: "Webhook not found", StatusCode: 404, Code: "ErrWebhookNotFound"}

// ErrRefreshWebhook is when a webhook already exists and is active and we tried to refresh it
var ErrRefreshWebhook = BHSError{Message: "Webhook already exists and is active", StatusCode: 400, Code: "ErrRefreshWebhook"}

// ErrGetAllWebhooks is when it failed to get all webhooks
var ErrGetAllWebhooks = BHSError{Message: "Failed to get all webhooks", StatusCode: 400, Code: "ErrGetAllWebhooks"}

// ErrDeleteWebhook is when it failed to delete a webhook
var ErrDeleteWebhook = BHSError{Message: "Failed to delete webhook", StatusCode: 400, Code: "ErrDeleteWebhook"}
32 changes: 21 additions & 11 deletions database/sql/webhooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package sql

import (
"context"
"database/sql"
"time"

"github.com/bitcoin-sv/block-headers-service/bhserrors"
"github.com/bitcoin-sv/block-headers-service/repository/dto"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
Expand Down Expand Up @@ -50,29 +50,34 @@ func (h *HeadersDb) CreateWebhook(ctx context.Context, rWebhook *dto.DbWebhook)
}()

if _, err := tx.NamedExecContext(ctx, h.db.Rebind(sqlInsertWebhook), *rWebhook); err != nil {
return errors.Wrap(err, "failed to insert webhook")
return bhserrors.ErrCreateWebhook.Wrap(err)
}
return errors.Wrap(tx.Commit(), "failed to commit tx")

err = tx.Commit()
if err != nil {
return bhserrors.ErrCreateWebhook.Wrap(err)
}

return nil
}

// GetWebhookByURL method will search and return webhook by url.
func (h *HeadersDb) GetWebhookByURL(ctx context.Context, url string) (*dto.DbWebhook, error) {
var rWebhook dto.DbWebhook
if err := h.db.GetContext(ctx, &rWebhook, h.db.Rebind(sqlGetWebhookByURL), url); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, errors.New("could not find webhook")
}
return nil, errors.Wrapf(err, "failed to get webhook using url %s", url)
return nil, bhserrors.ErrWebhookNotFound.Wrap(err)
}

return &rWebhook, nil
}

// GetAllWebhooks method will return all webhooks from db.
func (h *HeadersDb) GetAllWebhooks(ctx context.Context) ([]*dto.DbWebhook, error) {
var rWebhooks []*dto.DbWebhook
if err := h.db.SelectContext(ctx, &rWebhooks, h.db.Rebind(sqlGetAllWebhooks)); err != nil {
return nil, errors.Wrap(err, "failed to get all webhooks")
return nil, bhserrors.ErrGetAllWebhooks.Wrap(err)
}

return rWebhooks, nil
}

Expand All @@ -89,10 +94,15 @@ func (h *HeadersDb) DeleteWebhookByURL(ctx context.Context, url string) error {
params := map[string]interface{}{"url": url}

if _, err = tx.NamedExecContext(ctx, h.db.Rebind(sqlDeleteWebhookByURL), params); err != nil {
return errors.Wrap(err, "failed to delete webhook")
return bhserrors.ErrDeleteWebhook.Wrap(err)
}

return errors.Wrap(tx.Commit(), "failed to commit tx")
err = tx.Commit()
if err != nil {
return bhserrors.ErrDeleteWebhook.Wrap(err)
}

return nil
}

// UpdateWebhook method will update webhook in db.
Expand All @@ -112,6 +122,6 @@ func (h *HeadersDb) UpdateWebhook(ctx context.Context, url string, lastEmitTimes
if _, err := tx.ExecContext(ctx, h.db.Rebind(query), args...); err != nil {
return errors.Wrapf(err, "failed to update webhook with name %s", url)
}
return errors.Wrap(tx.Commit(), "failed to commit tx")

return errors.Wrap(tx.Commit(), "failed to commit tx")
}
4 changes: 2 additions & 2 deletions internal/tests/testrepository/webhooks_testrepository.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package testrepository

import (
"errors"
"fmt"

"github.com/bitcoin-sv/block-headers-service/bhserrors"
"github.com/bitcoin-sv/block-headers-service/notification"
)

Expand Down Expand Up @@ -35,7 +35,7 @@ func (r *WebhooksTestRepository) DeleteWebhookByURL(url string) error {
return nil
}
}
return errors.New("could not find webhook")
return bhserrors.ErrWebhookNotFound
}

// GetWebhookByURL returns webhook from db by given url.
Expand Down
6 changes: 3 additions & 3 deletions notification/webhooks_service.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package notification

import (
"errors"
"strings"

"github.com/bitcoin-sv/block-headers-service/bhserrors"
"github.com/bitcoin-sv/block-headers-service/config"
"github.com/rs/zerolog"
)
Expand Down Expand Up @@ -98,9 +98,9 @@ func (s *WebhooksService) refreshWebhook(url string) (*Webhook, error) {
w.ErrorsCount = 0
err = s.webhooks.UpdateWebhook(w)
if err != nil {
return nil, err
return nil, bhserrors.ErrRefreshWebhook.Wrap(err)
}
return w, nil
}
return nil, errors.New("webhook already exists and is active")
return nil, bhserrors.ErrRefreshWebhook
}
1 change: 0 additions & 1 deletion transports/http/endpoints/api/tips/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ func (h *handler) getTips(c *gin.Context) {
c.JSON(http.StatusOK, mapToTipStateResponse(tips))
} else {
bhserrors.ErrorResponse(c, err, h.log)
c.JSON(http.StatusBadRequest, err.Error())
}
}

Expand Down
21 changes: 12 additions & 9 deletions transports/http/endpoints/api/webhook/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package webhook
import (
"net/http"

"github.com/bitcoin-sv/block-headers-service/bhserrors"
"github.com/bitcoin-sv/block-headers-service/config"
"github.com/bitcoin-sv/block-headers-service/notification"
"github.com/bitcoin-sv/block-headers-service/service"
router "github.com/bitcoin-sv/block-headers-service/transports/http/endpoints/routes"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog"
)

// Webhooks is an interface which represents methods required for Webhooks service.
Expand All @@ -19,11 +21,12 @@ type Webhooks interface {

type handler struct {
service Webhooks
log *zerolog.Logger
}

// NewHandler creates new endpoint handler.
func NewHandler(s *service.Services) router.APIEndpoints {
return &handler{service: s.Webhooks}
return &handler{service: s.Webhooks, log: s.Logger}
}

// RegisterAPIEndpoints registers routes that are part of service API.
Expand Down Expand Up @@ -52,19 +55,19 @@ func (h *handler) registerWebhook(c *gin.Context) {
err := c.Bind(&reqBody)

if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
bhserrors.ErrorResponse(c, bhserrors.ErrBindBody.Wrap(err), h.log)
}

if reqBody.URL == "" {
c.JSON(http.StatusBadRequest, "URL is required")
bhserrors.ErrorResponse(c, bhserrors.ErrUrlBodyRequired, h.log)
return
}

webhook, err := h.service.CreateWebhook(reqBody.RequiredAuth.Type, reqBody.RequiredAuth.Header, reqBody.RequiredAuth.Token, reqBody.URL)
if err == nil {
c.JSON(http.StatusOK, webhook)
} else if webhook == nil {
c.JSON(http.StatusOK, err.Error())
} else {
bhserrors.ErrorResponse(c, err, h.log)
}
}

Expand All @@ -82,15 +85,15 @@ func (h *handler) registerWebhook(c *gin.Context) {
func (h *handler) getWebhook(c *gin.Context) {
url := c.Query("url")
if url == "" {
c.JSON(http.StatusBadRequest, "URL param is required")
bhserrors.ErrorResponse(c, bhserrors.ErrUrlParamRequired, h.log)
return
}
w, err := h.service.GetWebhookByURL(url)

if err == nil {
c.JSON(http.StatusOK, w)
} else {
c.JSON(http.StatusBadRequest, err.Error())
bhserrors.ErrorResponse(c, err, h.log)
}
}

Expand All @@ -108,14 +111,14 @@ func (h *handler) getWebhook(c *gin.Context) {
func (h *handler) revokeWebhook(c *gin.Context) {
url := c.Query("url")
if url == "" {
c.JSON(http.StatusBadRequest, "URL param is required")
bhserrors.ErrorResponse(c, bhserrors.ErrUrlParamRequired, h.log)
return
}
err := h.service.DeleteWebhook(url)

if err == nil {
c.JSON(http.StatusOK, "Webhook revoked")
} else {
c.JSON(http.StatusBadRequest, err.Error())
bhserrors.ErrorResponse(c, err, h.log)
}
}
11 changes: 4 additions & 7 deletions transports/http/endpoints/api/webhook/webhooks_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/bitcoin-sv/block-headers-service/internal/tests/testapp"
"github.com/bitcoin-sv/block-headers-service/transports/http/endpoints/api/webhook"
"github.com/stretchr/testify/require"
)

var webhookURL = "http://localhost:8080/api/v1/webhook/notify"
Expand Down Expand Up @@ -43,6 +44,7 @@ func TestMultipleIdenticalWebhooks(t *testing.T) {
// setup
bhs, cleanup := testapp.NewTestBlockHeaderService(t, testapp.WithAPIAuthorizationDisabled())
defer cleanup()
expectedBodyResponse := "{\"code\":\"ErrRefreshWebhook\",\"message\":\"Webhook already exists and is active\"}"

// when
res := bhs.API().Call(createWebhook())
Expand All @@ -55,16 +57,11 @@ func TestMultipleIdenticalWebhooks(t *testing.T) {
// when
res2 := bhs.API().Call(createWebhook())

if res2.Code != http.StatusOK {
if res2.Code != http.StatusBadRequest {
t.Fatalf("Expected to get status %d but instead got %d\n", http.StatusOK, res2.Code)
}

body, _ := io.ReadAll(res2.Body)
bodyStr := string(body)[1 : len(string(body))-1]

if bodyStr != "webhook already exists and is active" {
t.Fatalf("Expected message: 'webhook already exists and is active' but instead got '%s'\n", bodyStr)
}
require.JSONEq(t, expectedBodyResponse, res2.Body.String())
}

// TestRevokeWebhookEndpoint tests the webhook revocation.
Expand Down

0 comments on commit 7b50232

Please sign in to comment.