Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(SPV-1413) add method for getting webhooks to admin client #317

Merged
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: 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
Loading