Skip to content

Commit

Permalink
Feat(SPV-1413) add method for getting webhooks to admin client (#317)
Browse files Browse the repository at this point in the history
  • Loading branch information
ac4ch authored Jan 24, 2025
1 parent f16c37d commit 9b46040
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 6 deletions.
3 changes: 2 additions & 1 deletion .golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ linters:
disable-all: true
# Enable specific linter
# https://golangci-lint.run/usage/linters/#enabled-by-default
# WARN The linter 'exportloopref' is deprecated (since v1.60.2) due to: Since Go1.22 (loopvar) this linter is no longer relevant. Replaced by copyloopvar.
enable:
- bodyclose
- exhaustive
Expand All @@ -70,7 +71,7 @@ linters:
- unconvert
- ineffassign
- dogsled
- exportloopref
- copyloopvar
- sqlclosecheck
- nolintlint
- errcheck
Expand Down
13 changes: 13 additions & 0 deletions admin_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/bitcoin-sv/spv-wallet-go-client/internal/auth"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/constants"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/restyutil"
"github.com/bitcoin-sv/spv-wallet-go-client/notifications"
"github.com/bitcoin-sv/spv-wallet-go-client/queries"
"github.com/bitcoin-sv/spv-wallet/models"
"github.com/bitcoin-sv/spv-wallet/models/filter"
Expand Down Expand Up @@ -268,6 +269,18 @@ func (a *AdminAPI) UnsubscribeWebhook(ctx context.Context, cmd *commands.CancelW
return nil
}

// GetAllWebhooks retrieves all webhook subscriptions using the Admin Webhooks API.
// Accepts the context for controlling cancellation and timeout for the API request.
// Returns a list of Webhook objects or an error if the API request fails.
func (a *AdminAPI) GetAllWebhooks(ctx context.Context) ([]*notifications.Webhook, error) {
webhooks, err := a.webhooksAPI.AdminGetAllWebhooks(ctx)
if err != nil {
return nil, errutil.NewHTTPErrorFormatter(constants.AdminWebhooksAPI, "get all webhooks", err).FormatGetErr()
}

return webhooks, nil
}

// UTXOs fetches a paginated list of UTXOs via the Admin XPubs API.
// The response includes UTXOs along with pagination details, such as page number,
// sort order, and sorting field.
Expand Down
17 changes: 17 additions & 0 deletions internal/api/v1/admin/webhooks/webhooks_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/url"

"github.com/bitcoin-sv/spv-wallet-go-client/commands"
"github.com/bitcoin-sv/spv-wallet-go-client/notifications"
"github.com/go-resty/resty/v2"
)

Expand Down Expand Up @@ -45,6 +46,22 @@ func (a *API) UnsubscribeWebhook(ctx context.Context, cmd *commands.CancelWebhoo
return nil
}

func (a *API) AdminGetAllWebhooks(ctx context.Context) ([]*notifications.Webhook, error) {
var webhooks []*notifications.Webhook

_, err := a.httpClient.
R().
SetContext(ctx).
SetResult(&webhooks).
Get(a.url.String())

if err != nil {
return nil, fmt.Errorf("HTTP request failure: %w", err)
}

return webhooks, nil
}

func NewAPI(url *url.URL, httpClient *resty.Client) *API {
return &API{url: url.JoinPath(route), httpClient: httpClient}
}
62 changes: 62 additions & 0 deletions internal/api/v1/admin/webhooks/webhooks_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/bitcoin-sv/spv-wallet-go-client/commands"
"github.com/bitcoin-sv/spv-wallet-go-client/errors"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/testutils"
"github.com/bitcoin-sv/spv-wallet-go-client/notifications"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -94,3 +95,64 @@ func TestWebhooksAPI_UnsubscribeWebhook(t *testing.T) {
})
}
}

func TestWebhooksAPI_GetAllWebhooks(t *testing.T) {
tests := map[string]struct {
responder httpmock.Responder
expectedErr error
expectedResponse []*notifications.Webhook
}{
"HTTP GET /api/v1/admin/webhooks/subscriptions response: 200": {
responder: httpmock.NewJsonResponderOrPanic(http.StatusOK, []map[string]interface{}{
{
"url": "http://webhook1.com",
"tokenHeader": "Authorization",
"tokenValue": "abcd1234",
},
{
"url": "http://webhook2.com",
"tokenHeader": "Auth",
"tokenValue": "xyz5678",
},
}),
expectedResponse: []*notifications.Webhook{
{
URL: url,
TokenHeader: "Authorization",
TokenValue: "abcd1234",
},
{
URL: "http://webhook2.com",
TokenHeader: "Auth",
TokenValue: "xyz5678",
},
},
},
"HTTP GET /api/v1/admin/webhooks/subscriptions response: 400": {
expectedErr: testutils.NewBadRequestSPVError(),
responder: testutils.NewBadRequestSPVErrorResponder(),
},
"HTTP GET /api/v1/admin/webhooks/subscriptions response: 500": {
expectedErr: testutils.NewInternalServerSPVError(),
responder: testutils.NewInternalServerSPVErrorResponder(),
},
"HTTP GET /api/v1/admin/webhooks/subscriptions str response: 500": {
expectedErr: errors.ErrUnrecognizedAPIResponse,
responder: testutils.NewInternalServerSPVErrorStringResponder("unexpected internal server failure"),
},
}

url := testutils.FullAPIURL(t, webhooksURL)
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// when:
wallet, transport := testutils.GivenSPVAdminAPI(t)
transport.RegisterResponder(http.MethodGet, url, tc.responder)

// then:
webhooks, err := wallet.GetAllWebhooks(context.Background())
require.ErrorIs(t, err, tc.expectedErr)
require.ElementsMatch(t, tc.expectedResponse, webhooks)
})
}
}
22 changes: 17 additions & 5 deletions notifications/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,18 @@ func WithProcessors(count int) WebhookOpts {
type WebhookSubscriber interface {
AdminSubscribeWebhook(ctx context.Context, cmd *commands.CreateWebhookSubscription) error
AdminUnsubscribeWebhook(ctx context.Context, cmd *commands.CancelWebhookSubscription) error
AdminGetAllWebhooks(ctx context.Context) ([]*Webhook, error)
}

// Webhook - the webhook event receiver
type Webhook struct {
URL string
options *WebhookOptions
buffer chan *models.RawEvent
subscriber WebhookSubscriber
handlers *eventsMap
URL string `json:"url"`
TokenHeader string `json:"tokenHeader"`
TokenValue string `json:"tokenValue"`
options *WebhookOptions
buffer chan *models.RawEvent
subscriber WebhookSubscriber
handlers *eventsMap
}

// NewWebhook - creates a new webhook
Expand Down Expand Up @@ -126,6 +129,15 @@ func (w *Webhook) Unsubscribe(ctx context.Context) error {
return nil
}

// GetAllWebhooks - retrieves all subscribed webhooks from the spv-wallet
func (w *Webhook) GetAllWebhooks(ctx context.Context) ([]*Webhook, error) {
webhooks, err := w.subscriber.AdminGetAllWebhooks(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get all webhooks: %w", err)
}
return webhooks, nil
}

// HTTPHandler - returns an http handler for the webhook; it should be registered with the http server
func (w *Webhook) HTTPHandler() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
Expand Down

0 comments on commit 9b46040

Please sign in to comment.