From 3ca3e1d723328f3c7036fb48d8c37e762a843b42 Mon Sep 17 00:00:00 2001 From: ac4ch Date: Fri, 24 Jan 2025 07:04:51 +0100 Subject: [PATCH 1/5] feat(spv-1413) aded method to get all webhooks --- .golangci-lint.yml | 3 +- admin_api.go | 21 +++++- .../api/v1/admin/webhooks/webhooks_api.go | 24 ++++++- .../v1/admin/webhooks/webhooks_api_test.go | 68 ++++++++++++++++++- notifications/webhook.go | 25 +++++-- 5 files changed, 128 insertions(+), 13 deletions(-) diff --git a/.golangci-lint.yml b/.golangci-lint.yml index 722d9c2e..2fc6f702 100644 --- a/.golangci-lint.yml +++ b/.golangci-lint.yml @@ -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 @@ -70,7 +71,7 @@ linters: - unconvert - ineffassign - dogsled - - exportloopref + - copyloopvar - sqlclosecheck - nolintlint - errcheck diff --git a/admin_api.go b/admin_api.go index 472bdb47..17fc953a 100644 --- a/admin_api.go +++ b/admin_api.go @@ -5,6 +5,10 @@ import ( "fmt" "net/url" + "github.com/bitcoin-sv/spv-wallet/models" + "github.com/bitcoin-sv/spv-wallet/models/filter" + "github.com/bitcoin-sv/spv-wallet/models/response" + "github.com/bitcoin-sv/spv-wallet-go-client/commands" "github.com/bitcoin-sv/spv-wallet-go-client/config" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/accesskeys" @@ -22,10 +26,8 @@ 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" - "github.com/bitcoin-sv/spv-wallet/models/response" ) // AdminAPI provides a simplified interface for interacting with admin-related APIs. @@ -268,6 +270,19 @@ 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 { + msg := "get all webhooks" + return nil, errutil.NewHTTPErrorFormatter(constants.AdminWebhooksAPI, msg, 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. diff --git a/internal/api/v1/admin/webhooks/webhooks_api.go b/internal/api/v1/admin/webhooks/webhooks_api.go index b412a715..e38d32d8 100644 --- a/internal/api/v1/admin/webhooks/webhooks_api.go +++ b/internal/api/v1/admin/webhooks/webhooks_api.go @@ -5,8 +5,10 @@ import ( "fmt" "net/url" - "github.com/bitcoin-sv/spv-wallet-go-client/commands" "github.com/go-resty/resty/v2" + + "github.com/bitcoin-sv/spv-wallet-go-client/commands" + "github.com/bitcoin-sv/spv-wallet-go-client/notifications" ) const ( @@ -45,6 +47,26 @@ 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 + + resp, err := a.httpClient. + R(). + SetContext(ctx). + SetResult(&webhooks). + Get(a.url.String()) + + if err != nil { + return nil, fmt.Errorf("HTTP request failure: %w", err) + } + + if resp.IsError() { + return nil, fmt.Errorf("API error: %s", resp.String()) + } + + return webhooks, nil +} + func NewAPI(url *url.URL, httpClient *resty.Client) *API { return &API{url: url.JoinPath(route), httpClient: httpClient} } diff --git a/internal/api/v1/admin/webhooks/webhooks_api_test.go b/internal/api/v1/admin/webhooks/webhooks_api_test.go index 06c769ac..a4e63e50 100644 --- a/internal/api/v1/admin/webhooks/webhooks_api_test.go +++ b/internal/api/v1/admin/webhooks/webhooks_api_test.go @@ -5,11 +5,12 @@ import ( "net/http" "testing" + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/require" + "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/jarcoal/httpmock" - "github.com/stretchr/testify/require" ) const ( @@ -94,3 +95,66 @@ func TestWebhooksAPI_UnsubscribeWebhook(t *testing.T) { }) } } + +func TestWebhooksAPI_GetAllWebhooks(t *testing.T) { + tests := map[string]struct { + responder httpmock.Responder + expectedErr error + }{ + "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", + }, + }), + }, + "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) + + if tc.expectedErr == nil { + require.NotNil(t, webhooks) + require.Len(t, webhooks, 2) + + require.Equal(t, "http://webhook1.com", webhooks[0].URL) + require.Equal(t, "Authorization", webhooks[0].TokenHeader) + require.Equal(t, "abcd1234", webhooks[0].TokenValue) + + require.Equal(t, "http://webhook2.com", webhooks[1].URL) + require.Equal(t, "Auth", webhooks[1].TokenHeader) + require.Equal(t, "xyz5678", webhooks[1].TokenValue) + } else { + require.Nil(t, webhooks) + } + }) + } +} diff --git a/notifications/webhook.go b/notifications/webhook.go index 9d41f9d1..a4fef3ac 100644 --- a/notifications/webhook.go +++ b/notifications/webhook.go @@ -9,8 +9,9 @@ import ( "runtime" "time" - "github.com/bitcoin-sv/spv-wallet-go-client/commands" "github.com/bitcoin-sv/spv-wallet/models" + + "github.com/bitcoin-sv/spv-wallet-go-client/commands" ) // WebhookOptions - options for the webhook @@ -69,15 +70,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 @@ -126,6 +130,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) { From 04e9d7dc435246d5b5d63747331495a69562c6e6 Mon Sep 17 00:00:00 2001 From: ac4ch Date: Fri, 24 Jan 2025 07:05:16 +0100 Subject: [PATCH 2/5] feat(spv-1413) adjusting imports via lint --- admin_api.go | 7 +++---- internal/api/v1/admin/webhooks/webhooks_api.go | 3 +-- internal/api/v1/admin/webhooks/webhooks_api_test.go | 5 ++--- notifications/webhook.go | 3 +-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/admin_api.go b/admin_api.go index 17fc953a..d7cd77cc 100644 --- a/admin_api.go +++ b/admin_api.go @@ -5,10 +5,6 @@ import ( "fmt" "net/url" - "github.com/bitcoin-sv/spv-wallet/models" - "github.com/bitcoin-sv/spv-wallet/models/filter" - "github.com/bitcoin-sv/spv-wallet/models/response" - "github.com/bitcoin-sv/spv-wallet-go-client/commands" "github.com/bitcoin-sv/spv-wallet-go-client/config" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/accesskeys" @@ -28,6 +24,9 @@ import ( "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" + "github.com/bitcoin-sv/spv-wallet/models/response" ) // AdminAPI provides a simplified interface for interacting with admin-related APIs. diff --git a/internal/api/v1/admin/webhooks/webhooks_api.go b/internal/api/v1/admin/webhooks/webhooks_api.go index e38d32d8..2a59798f 100644 --- a/internal/api/v1/admin/webhooks/webhooks_api.go +++ b/internal/api/v1/admin/webhooks/webhooks_api.go @@ -5,10 +5,9 @@ import ( "fmt" "net/url" - "github.com/go-resty/resty/v2" - "github.com/bitcoin-sv/spv-wallet-go-client/commands" "github.com/bitcoin-sv/spv-wallet-go-client/notifications" + "github.com/go-resty/resty/v2" ) const ( diff --git a/internal/api/v1/admin/webhooks/webhooks_api_test.go b/internal/api/v1/admin/webhooks/webhooks_api_test.go index a4e63e50..48b5a296 100644 --- a/internal/api/v1/admin/webhooks/webhooks_api_test.go +++ b/internal/api/v1/admin/webhooks/webhooks_api_test.go @@ -5,12 +5,11 @@ import ( "net/http" "testing" - "github.com/jarcoal/httpmock" - "github.com/stretchr/testify/require" - "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/jarcoal/httpmock" + "github.com/stretchr/testify/require" ) const ( diff --git a/notifications/webhook.go b/notifications/webhook.go index a4fef3ac..a49481a0 100644 --- a/notifications/webhook.go +++ b/notifications/webhook.go @@ -9,9 +9,8 @@ import ( "runtime" "time" - "github.com/bitcoin-sv/spv-wallet/models" - "github.com/bitcoin-sv/spv-wallet-go-client/commands" + "github.com/bitcoin-sv/spv-wallet/models" ) // WebhookOptions - options for the webhook From e8bc8145d36b54c94e2fc503b19618ac57225783 Mon Sep 17 00:00:00 2001 From: ac4ch Date: Fri, 24 Jan 2025 07:29:13 +0100 Subject: [PATCH 3/5] feat(spv-1413) corrected test per review comment --- .../v1/admin/webhooks/webhooks_api_test.go | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/internal/api/v1/admin/webhooks/webhooks_api_test.go b/internal/api/v1/admin/webhooks/webhooks_api_test.go index 48b5a296..d6c8d7d3 100644 --- a/internal/api/v1/admin/webhooks/webhooks_api_test.go +++ b/internal/api/v1/admin/webhooks/webhooks_api_test.go @@ -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" ) @@ -97,8 +98,9 @@ func TestWebhooksAPI_UnsubscribeWebhook(t *testing.T) { func TestWebhooksAPI_GetAllWebhooks(t *testing.T) { tests := map[string]struct { - responder httpmock.Responder - expectedErr error + 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{}{ @@ -113,6 +115,18 @@ func TestWebhooksAPI_GetAllWebhooks(t *testing.T) { "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(), @@ -137,23 +151,8 @@ func TestWebhooksAPI_GetAllWebhooks(t *testing.T) { // then: webhooks, err := wallet.GetAllWebhooks(context.Background()) - require.ErrorIs(t, err, tc.expectedErr) - - if tc.expectedErr == nil { - require.NotNil(t, webhooks) - require.Len(t, webhooks, 2) - - require.Equal(t, "http://webhook1.com", webhooks[0].URL) - require.Equal(t, "Authorization", webhooks[0].TokenHeader) - require.Equal(t, "abcd1234", webhooks[0].TokenValue) - - require.Equal(t, "http://webhook2.com", webhooks[1].URL) - require.Equal(t, "Auth", webhooks[1].TokenHeader) - require.Equal(t, "xyz5678", webhooks[1].TokenValue) - } else { - require.Nil(t, webhooks) - } + require.ElementsMatch(t, tc.expectedResponse, webhooks) }) } } From 86278633b5c614aa320c8c352b348d94bf9524fe Mon Sep 17 00:00:00 2001 From: augustyn chmiel <149666032+ac4ch@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:50:12 +0100 Subject: [PATCH 4/5] Update admin_api.go Co-authored-by: chris-4chain <152964795+chris-4chain@users.noreply.github.com> --- admin_api.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/admin_api.go b/admin_api.go index d7cd77cc..d72da24b 100644 --- a/admin_api.go +++ b/admin_api.go @@ -275,8 +275,7 @@ func (a *AdminAPI) UnsubscribeWebhook(ctx context.Context, cmd *commands.CancelW func (a *AdminAPI) GetAllWebhooks(ctx context.Context) ([]*notifications.Webhook, error) { webhooks, err := a.webhooksAPI.AdminGetAllWebhooks(ctx) if err != nil { - msg := "get all webhooks" - return nil, errutil.NewHTTPErrorFormatter(constants.AdminWebhooksAPI, msg, err).FormatGetErr() + return nil, errutil.NewHTTPErrorFormatter(constants.AdminWebhooksAPI, "get all webhooks", err).FormatGetErr() } return webhooks, nil From 861a2dfeec6511c3929b5241b38b9d6bd5f8f4f9 Mon Sep 17 00:00:00 2001 From: augustyn chmiel <149666032+ac4ch@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:50:32 +0100 Subject: [PATCH 5/5] Update internal/api/v1/admin/webhooks/webhooks_api.go Co-authored-by: chris-4chain <152964795+chris-4chain@users.noreply.github.com> --- internal/api/v1/admin/webhooks/webhooks_api.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/api/v1/admin/webhooks/webhooks_api.go b/internal/api/v1/admin/webhooks/webhooks_api.go index 2a59798f..bc70397e 100644 --- a/internal/api/v1/admin/webhooks/webhooks_api.go +++ b/internal/api/v1/admin/webhooks/webhooks_api.go @@ -49,7 +49,7 @@ func (a *API) UnsubscribeWebhook(ctx context.Context, cmd *commands.CancelWebhoo func (a *API) AdminGetAllWebhooks(ctx context.Context) ([]*notifications.Webhook, error) { var webhooks []*notifications.Webhook - resp, err := a.httpClient. + _, err := a.httpClient. R(). SetContext(ctx). SetResult(&webhooks). @@ -59,10 +59,6 @@ func (a *API) AdminGetAllWebhooks(ctx context.Context) ([]*notifications.Webhook return nil, fmt.Errorf("HTTP request failure: %w", err) } - if resp.IsError() { - return nil, fmt.Errorf("API error: %s", resp.String()) - } - return webhooks, nil }