From fdb4ec588d72f9df6f313179714627d8d3de812f Mon Sep 17 00:00:00 2001 From: mgosek-4chain Date: Mon, 2 Dec 2024 13:48:40 +0100 Subject: [PATCH 1/6] refactor(SPV-1231): add admin paymail API implementation. --- admin_api.go | 67 ++++++- commands/paymails.go | 13 ++ commands/users.go | 6 +- examples/README.md | 4 + examples/Taskfile.yml | 24 +++ .../create_paymail_as_admin.go | 47 +++++ .../delete_paymail_as_admin.go | 54 ++++++ examples/exampleutil/exampleutil.go | 10 + .../fetch_paymail_as_admin.go | 51 +++++ .../fetch_paymails_as_admin.go | 28 +++ .../admin/paymails/paymail_filter_builder.go | 33 ++++ .../paymails/paymail_filter_builder_test.go | 84 +++++++++ .../api/v1/admin/paymails/paymails_api.go | 112 +++++++++++ .../v1/admin/paymails/paymails_api_test.go | 177 ++++++++++++++++++ .../paymailstest/get_paymail_200.json | 12 ++ .../paymailstest/get_paymails_200.json | 34 ++++ .../paymailstest/paymail_api_fixtures.go | 105 +++++++++++ .../paymailstest/post_paymail_200.json | 12 ++ queries/paymails.go | 46 +++++ 19 files changed, 915 insertions(+), 4 deletions(-) create mode 100644 commands/paymails.go create mode 100644 examples/create_paymail_as_admin/create_paymail_as_admin.go create mode 100644 examples/delete_paymail_as_admin/delete_paymail_as_admin.go create mode 100644 examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go create mode 100644 examples/fetch_paymails_as_admin/fetch_paymails_as_admin.go create mode 100644 internal/api/v1/admin/paymails/paymail_filter_builder.go create mode 100644 internal/api/v1/admin/paymails/paymail_filter_builder_test.go create mode 100644 internal/api/v1/admin/paymails/paymails_api.go create mode 100644 internal/api/v1/admin/paymails/paymails_api_test.go create mode 100644 internal/api/v1/admin/paymails/paymailstest/get_paymail_200.json create mode 100644 internal/api/v1/admin/paymails/paymailstest/get_paymails_200.json create mode 100644 internal/api/v1/admin/paymails/paymailstest/paymail_api_fixtures.go create mode 100644 internal/api/v1/admin/paymails/paymailstest/post_paymail_200.json create mode 100644 queries/paymails.go diff --git a/admin_api.go b/admin_api.go index d998a812..5842be73 100644 --- a/admin_api.go +++ b/admin_api.go @@ -8,6 +8,7 @@ import ( bip32 "github.com/bitcoin-sv/go-sdk/compat/bip32" "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/paymails" xpubs "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/users" "github.com/bitcoin-sv/spv-wallet-go-client/internal/auth" "github.com/bitcoin-sv/spv-wallet-go-client/internal/restyutil" @@ -25,7 +26,8 @@ import ( // Methods may return wrapped errors, including models.SPVError or // ErrUnrecognizedAPIResponse, depending on the behavior of the SPV Wallet API. type AdminAPI struct { - xpubsAPI *xpubs.API // Internal API for managing operations related to XPubs. + xpubsAPI *xpubs.API // Internal API for managing operations related to XPubs. + paymailsAPI *paymails.API } // CreateXPub creates a new XPub record via the Admin XPubs API. @@ -60,6 +62,64 @@ func (a *AdminAPI) XPubs(ctx context.Context, opts ...queries.XPubQueryOption) ( return res, nil } +// Paymails retrieves a paginated list of paymail addresses via the Admin Paymails API. +// The response includes user paymails along with pagination metadata, such as +// the current page number, sort order, and the field used for sorting (sortBy). +// +// Query parameters can be configured using optional query options. These options allow +// filtering based on metadata, pagination settings, or specific paymail attributes. +// +// The API response is unmarshaled into a *queries.PaymailAddressPage struct. +// Returns an error if the API request fails or the response cannot be decoded. +func (a *AdminAPI) Paymails(ctx context.Context, opts ...queries.PaymailQueryOption) (*queries.PaymailAddressPage, error) { + res, err := a.paymailsAPI.Paymails(ctx, opts...) + if err != nil { + return nil, paymails.HTTPErrorFormatter("failed to retrieve paymail addresses page", err).FormatGetErr() + } + + return res, nil +} + +// Paymail retrieves the paymail address associated with the specified ID via the Admin Paymails API. +// The response is expected to be unmarshaled into a *response.PaymailAddress struct. +// Returns an error if the request fails or the response cannot be decoded. +func (a *AdminAPI) Paymail(ctx context.Context, ID string) (*response.PaymailAddress, error) { + res, err := a.paymailsAPI.Paymail(ctx, ID) + if err != nil { + msg := fmt.Sprintf("failed retrieve paymail address with ID: %s", ID) + return nil, paymails.HTTPErrorFormatter(msg, err).FormatGetErr() + } + + return res, nil +} + +// CreatePaymail creates a new paymial address record via the Admin Paymails API. +// The provided command contains the necessary parameters to define the paymail address record. +// +// The API response is unmarshaled into a *response.Xpub PaymailAddress. +// Returns an error if the API request fails or the response cannot be decoded. +func (a *AdminAPI) CreatePaymail(ctx context.Context, cmd *commands.CreatePaymail) (*response.PaymailAddress, error) { + res, err := a.paymailsAPI.CreatePaymail(ctx, cmd) + if err != nil { + return nil, paymails.HTTPErrorFormatter("failed to create paymail address", err).FormatPostErr() + } + + return res, nil +} + +// DeletePaymail deletes a paymail address with via the Admin Paymails API. +// It returns an error if the API request fails. A nil error indicates that the paymail +// was successfully deleted. +func (a *AdminAPI) DeletePaymail(ctx context.Context, address string) error { + err := a.paymailsAPI.DeletePaymail(ctx, address) + if err != nil { + msg := fmt.Sprintf("failed to remove paymail address: %s", address) + return paymails.HTTPErrorFormatter(msg, err).FormatGetErr() + } + + return nil +} + // NewAdminAPIWithXPriv initializes a new AdminAPI instance using an extended private key (xPriv). // This function configures the API client with the provided configuration and uses the xPriv key for authentication. // If any step fails, an appropriate error is returned. @@ -106,5 +166,8 @@ func initAdminAPI(cfg config.Config, auth authenticator) (*AdminAPI, error) { } httpClient := restyutil.NewHTTPClient(cfg, auth) - return &AdminAPI{xpubsAPI: xpubs.NewAPI(url, httpClient)}, nil + return &AdminAPI{ + xpubsAPI: xpubs.NewAPI(url, httpClient), + paymailsAPI: paymails.NewAPI(url, httpClient), + }, nil } diff --git a/commands/paymails.go b/commands/paymails.go new file mode 100644 index 00000000..23436f1d --- /dev/null +++ b/commands/paymails.go @@ -0,0 +1,13 @@ +package commands + +import "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" + +// CreatePaymail defines the parameters required to create a new paymail address, +// including associated metadata such as the public name and avatar. +type CreatePaymail struct { + Metadata querybuilders.Metadata `json:"metadata"` // Metadata associated with the paymail as key-value pairs. + Key string `json:"key"` // The xpub key linked to the paymail. + Address string `json:"address"` // The paymail address to be created. + PublicName string `json:"public_name"` // The public display name associated with the paymail. + Avatar string `json:"avatar"` // The URL of the paymail's avatar image. +} diff --git a/commands/users.go b/commands/users.go index d689003d..3519962a 100644 --- a/commands/users.go +++ b/commands/users.go @@ -1,13 +1,15 @@ package commands +import "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" + // UpdateXPubMetadata contains the parameters needed to update the metadata // associated with the current user's xpub. type UpdateXPubMetadata struct { - Metadata map[string]any `json:"metadata"` // Key-value pairs representing the xpub metadata + Metadata querybuilders.Metadata `json:"metadata"` // Key-value pairs representing the xpub metadata } // GenerateAccessKey contains the parameters needed to generate a new access key // for the current user, including any associated metadata. type GenerateAccessKey struct { - Metadata map[string]any `json:"metadata"` // Key-value pairs representing the access key metadata + Metadata querybuilders.Metadata `json:"metadata"` // Key-value pairs representing the access key metadata } diff --git a/examples/README.md b/examples/README.md index d9282907..e3263eab 100644 --- a/examples/README.md +++ b/examples/README.md @@ -21,7 +21,11 @@ the wallet client package during interaction wit the SPV Wallet API. ``` task: [default] task --list task: Available tasks for this project: +* create-paymail-as-admin: Create a paymail address. * default: Display all available tasks. +* delete-paymail-as-admin: Delete paymail address. +* fetch-paymail-as-admin: Fetch paymail with a given address. +* fetch-paymails-as-admin: Fetch paymails page. * fetch-user-contact-by-paymail: Fetch user contact by given paymail. * fetch-user-contacts: Fetch user contacts page. * fetch-user-merkleroots: Fetch user Merkle roots page. diff --git a/examples/Taskfile.yml b/examples/Taskfile.yml index c74fd13d..48ee7dc9 100644 --- a/examples/Taskfile.yml +++ b/examples/Taskfile.yml @@ -14,6 +14,30 @@ tasks: - go run ../walletkeys/cmd/main.go - echo "==================================================================" + fetch-paymails-as-admin: + desc: "Fetch paymails page." + silent: true + cmds: + - go run ./fetch_paymails_as_admin/fetch_paymails_as_admin.go + + fetch-paymail-as-admin: + desc: "Fetch paymail with a given address." + silent: true + cmds: + - go run ./fetch_paymail_as_admin/fetch_paymail_as_admin.go + + create-paymail-as-admin: + desc: "Create a paymail address." + silent: true + cmds: + - go run ./create_paymail_as_admin/create_paymail_as_admin.go + + delete-paymail-as-admin: + desc: "Delete paymail address." + silent: true + cmds: + - go run ./delete_paymail_as_admin/delete_paymail_as_admin.go + fetch-user-shared-config: desc: "Fetch user shared configuration." silent: true diff --git a/examples/create_paymail_as_admin/create_paymail_as_admin.go b/examples/create_paymail_as_admin/create_paymail_as_admin.go new file mode 100644 index 00000000..f015b531 --- /dev/null +++ b/examples/create_paymail_as_admin/create_paymail_as_admin.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + "log" + + wallet "github.com/bitcoin-sv/spv-wallet-go-client" + "github.com/bitcoin-sv/spv-wallet-go-client/commands" + "github.com/bitcoin-sv/spv-wallet-go-client/examples" + "github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" + "github.com/bitcoin-sv/spv-wallet-go-client/walletkeys" +) + +func main() { + ctx := context.Background() + keys, err := walletkeys.RandomKeys() + if err != nil { + log.Fatal(err) + } + + adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv) + if err != nil { + log.Fatal(err) + } + + xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{ + Metadata: map[string]any{"xpub_key": "xpub_val"}, + XPub: keys.XPub(), + }) + if err != nil { + log.Fatal(err) + } + exampleutil.Print("[HTTP POST][Step 1] Create xPub - api/v1/admin/users", xPub) + + addr := exampleutil.RandomPaymail() + paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{ + Key: keys.XPub(), + Address: addr, + Metadata: querybuilders.Metadata{"key": "value"}, + }) + if err != nil { + log.Fatal(err) + } + + exampleutil.Print("[HTTP POST][Step 2] Create Paymail - api/v1/admin/paymails", paymail) +} diff --git a/examples/delete_paymail_as_admin/delete_paymail_as_admin.go b/examples/delete_paymail_as_admin/delete_paymail_as_admin.go new file mode 100644 index 00000000..2b0bd508 --- /dev/null +++ b/examples/delete_paymail_as_admin/delete_paymail_as_admin.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + + wallet "github.com/bitcoin-sv/spv-wallet-go-client" + "github.com/bitcoin-sv/spv-wallet-go-client/commands" + "github.com/bitcoin-sv/spv-wallet-go-client/examples" + "github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" + "github.com/bitcoin-sv/spv-wallet-go-client/walletkeys" +) + +func main() { + ctx := context.Background() + keys, err := walletkeys.RandomKeys() + if err != nil { + log.Fatal(err) + } + + adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv) + if err != nil { + log.Fatal(err) + } + + xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{ + Metadata: map[string]any{"xpub_key": "xpub_val"}, + XPub: keys.XPub(), + }) + if err != nil { + log.Fatal(err) + } + exampleutil.Print("[HTTP POST][Step 1] Create xPub - api/v1/admin/users", xPub) + + addr := exampleutil.RandomPaymail() + paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{ + Key: keys.XPub(), + Address: addr, + Metadata: querybuilders.Metadata{"key": "value"}, + }) + if err != nil { + log.Fatal(err) + } + exampleutil.Print("[HTTP POST][Step 2] Create Paymail - api/v1/admin/paymails", paymail) + + err = adminAPI.DeletePaymail(ctx, addr) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("[HTTP DELETE][Step 3] Delete Paymail: %s - api/v1/admin/paymails", addr) +} diff --git a/examples/exampleutil/exampleutil.go b/examples/exampleutil/exampleutil.go index a9b9a7eb..0cee8107 100644 --- a/examples/exampleutil/exampleutil.go +++ b/examples/exampleutil/exampleutil.go @@ -5,6 +5,9 @@ import ( "fmt" "log" "strings" + "time" + + "math/rand" "github.com/bitcoin-sv/spv-wallet-go-client/config" ) @@ -21,3 +24,10 @@ func Print(s string, a any) { } fmt.Println(string(res)) } + +func RandomPaymail() string { + seed := time.Now().UnixNano() + n := rand.New(rand.NewSource(seed)).Intn(500) + addr := fmt.Sprintf("john.doe.%dtest@4chain.test.com", n) + return addr +} diff --git a/examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go b/examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go new file mode 100644 index 00000000..0e06b8e1 --- /dev/null +++ b/examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "log" + + wallet "github.com/bitcoin-sv/spv-wallet-go-client" + "github.com/bitcoin-sv/spv-wallet-go-client/commands" + "github.com/bitcoin-sv/spv-wallet-go-client/examples" + "github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" + "github.com/bitcoin-sv/spv-wallet-go-client/walletkeys" +) + +func main() { + ctx := context.Background() + keys, err := walletkeys.RandomKeys() + if err != nil { + log.Fatal(err) + } + + adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv) + if err != nil { + log.Fatal(err) + } + + xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{ + Metadata: map[string]any{"xpub_key": "xpub_val"}, + XPub: keys.XPub(), + }) + if err != nil { + log.Fatal(err) + } + exampleutil.Print("[HTTP POST][Step 1] Create xPub - api/v1/admin/users", xPub) + + paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{ + Key: keys.XPub(), + Address: exampleutil.RandomPaymail(), + Metadata: querybuilders.Metadata{"key": "value"}, + }) + if err != nil { + log.Fatal(err) + } + exampleutil.Print("[HTTP POST][Step 2] Create Paymail - api/v1/admin/paymails", paymail) + + paymail, err = adminAPI.Paymail(ctx, paymail.ID) + if err != nil { + log.Fatal(err) + } + exampleutil.Print("[HTTP GET][Step 3] Fetch Paymail - api/v1/admin/paymails", paymail) +} diff --git a/examples/fetch_paymails_as_admin/fetch_paymails_as_admin.go b/examples/fetch_paymails_as_admin/fetch_paymails_as_admin.go new file mode 100644 index 00000000..0414bef0 --- /dev/null +++ b/examples/fetch_paymails_as_admin/fetch_paymails_as_admin.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "log" + + wallet "github.com/bitcoin-sv/spv-wallet-go-client" + "github.com/bitcoin-sv/spv-wallet-go-client/examples" + "github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil" + "github.com/bitcoin-sv/spv-wallet-go-client/queries" + "github.com/bitcoin-sv/spv-wallet/models/filter" +) + +func main() { + adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv) + if err != nil { + log.Fatal(err) + } + + page, err := adminAPI.Paymails(context.Background(), queries.PaymailQueryWithPageFilter(filter.Page{ + Size: 3, + })) + if err != nil { + log.Fatal(err) + } + + exampleutil.Print("[HTTP GET] Paymails page - api/v1/admin/paymails", page) +} diff --git a/internal/api/v1/admin/paymails/paymail_filter_builder.go b/internal/api/v1/admin/paymails/paymail_filter_builder.go new file mode 100644 index 00000000..3d478c5e --- /dev/null +++ b/internal/api/v1/admin/paymails/paymail_filter_builder.go @@ -0,0 +1,33 @@ +package paymails + +import ( + "fmt" + "net/url" + + "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" + "github.com/bitcoin-sv/spv-wallet/models/filter" +) + +type paymailFilterBuilder struct { + paymailFilter filter.AdminPaymailFilter + modelFilterBuilder querybuilders.ModelFilterBuilder +} + +func (p *paymailFilterBuilder) Build() (url.Values, error) { + modelFilterBuilder, err := p.modelFilterBuilder.Build() + if err != nil { + return nil, fmt.Errorf("failed to build model filter query params: %w", err) + } + + params := querybuilders.NewExtendedURLValues() + if len(modelFilterBuilder) > 0 { + params.Append(modelFilterBuilder) + } + + params.AddPair("id", p.paymailFilter.ID) + params.AddPair("xpubId", p.paymailFilter.XpubID) + params.AddPair("alias", p.paymailFilter.Alias) + params.AddPair("domain", p.paymailFilter.Domain) + params.AddPair("publicName", p.paymailFilter.PublicName) + return params.Values, nil +} diff --git a/internal/api/v1/admin/paymails/paymail_filter_builder_test.go b/internal/api/v1/admin/paymails/paymail_filter_builder_test.go new file mode 100644 index 00000000..beb86c7d --- /dev/null +++ b/internal/api/v1/admin/paymails/paymail_filter_builder_test.go @@ -0,0 +1,84 @@ +package paymails + +import ( + "net/url" + "testing" + + "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/paymails/paymailstest" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" + "github.com/bitcoin-sv/spv-wallet/models/filter" + "github.com/stretchr/testify/require" +) + +func TestPaymailFilterBuilder_Build(t *testing.T) { + tests := map[string]struct { + filter filter.AdminPaymailFilter + expectedParams url.Values + expectedErr error + }{ + "admin paymail filter: zero values": { + expectedParams: make(url.Values), + }, + "admin paymail filter: filter with only 'id' field set": { + filter: filter.AdminPaymailFilter{ + ID: paymailstest.Ptr("b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"), + }, + expectedParams: url.Values{ + "id": []string{"b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"}, + }, + }, + "admin paymail filter: filter with only 'xPubId' field set": { + filter: filter.AdminPaymailFilter{ + XpubID: paymailstest.Ptr("7d373830-1d74-4c4b-a435-04ce09398027"), + }, + expectedParams: url.Values{ + "xpubId": []string{"7d373830-1d74-4c4b-a435-04ce09398027"}, + }, + }, + "admin paymail filter: filter with only 'alias' field set": { + filter: filter.AdminPaymailFilter{ + Alias: paymailstest.Ptr("alias"), + }, + expectedParams: url.Values{ + "alias": []string{"alias"}, + }, + }, + "admin paymail filter: filter with only 'public name' field set": { + filter: filter.AdminPaymailFilter{ + PublicName: paymailstest.Ptr("Alice"), + }, + expectedParams: url.Values{ + "publicName": []string{"Alice"}, + }, + }, + "admin paymail filter: all fields set": { + filter: filter.AdminPaymailFilter{ + ID: paymailstest.Ptr("b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"), + XpubID: paymailstest.Ptr("7d373830-1d74-4c4b-a435-04ce09398027"), + PublicName: paymailstest.Ptr("Alice"), + Alias: paymailstest.Ptr("alias"), + }, + expectedParams: url.Values{ + "publicName": []string{"Alice"}, + "xpubId": []string{"7d373830-1d74-4c4b-a435-04ce09398027"}, + "alias": []string{"alias"}, + "id": []string{"b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"}, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // when: + queryBuilder := paymailFilterBuilder{ + paymailFilter: tc.filter, + modelFilterBuilder: querybuilders.ModelFilterBuilder{ModelFilter: tc.filter.ModelFilter}, + } + + // then: + got, err := queryBuilder.Build() + require.ErrorIs(t, tc.expectedErr, err) + require.Equal(t, tc.expectedParams, got) + }) + } +} diff --git a/internal/api/v1/admin/paymails/paymails_api.go b/internal/api/v1/admin/paymails/paymails_api.go new file mode 100644 index 00000000..494641b5 --- /dev/null +++ b/internal/api/v1/admin/paymails/paymails_api.go @@ -0,0 +1,112 @@ +package paymails + +import ( + "context" + "fmt" + "net/url" + + "github.com/bitcoin-sv/spv-wallet-go-client/commands" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/errutil" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" + "github.com/bitcoin-sv/spv-wallet-go-client/queries" + "github.com/bitcoin-sv/spv-wallet/models/response" + "github.com/go-resty/resty/v2" +) + +const route = "api/v1/admin/paymails" + +type API struct { + httpClient *resty.Client + url *url.URL +} + +func (a *API) DeletePaymail(ctx context.Context, address string) error { + type body struct { + Address string `json:"address"` + } + + _, err := a.httpClient. + R(). + SetContext(ctx). + SetBody(body{Address: address}). + Delete(a.url.JoinPath(address).String()) + if err != nil { + return fmt.Errorf("HTTP response failure: %w", err) + } + + return nil +} + +func (a *API) CreatePaymail(ctx context.Context, cmd *commands.CreatePaymail) (*response.PaymailAddress, error) { + var result response.PaymailAddress + _, err := a.httpClient. + R(). + SetContext(ctx). + SetResult(&result). + SetBody(cmd). + Post(a.url.String()) + if err != nil { + return nil, fmt.Errorf("HTTP response failure: %w", err) + } + + return &result, nil +} + +func (a *API) Paymail(ctx context.Context, ID string) (*response.PaymailAddress, error) { + var result response.PaymailAddress + _, err := a.httpClient. + R(). + SetContext(ctx). + SetResult(&result). + Get(a.url.JoinPath(ID).String()) + if err != nil { + return nil, fmt.Errorf("HTTP response failure: %w", err) + } + + return &result, nil +} + +func (a *API) Paymails(ctx context.Context, opts ...queries.PaymailQueryOption) (*queries.PaymailAddressPage, error) { + var query queries.PaymailQuery + for _, o := range opts { + o(&query) + } + + queryBuilder := querybuilders.NewQueryBuilder( + querybuilders.WithMetadataFilter(query.Metadata), + querybuilders.WithPageFilter(query.PageFilter), + querybuilders.WithFilterQueryBuilder(&paymailFilterBuilder{ + paymailFilter: query.PaymailFilter, + modelFilterBuilder: querybuilders.ModelFilterBuilder{ModelFilter: query.PaymailFilter.ModelFilter}, + }), + ) + params, err := queryBuilder.Build() + if err != nil { + return nil, fmt.Errorf("failed to build paymail address query params: %w", err) + } + + var result queries.PaymailAddressPage + _, err = a.httpClient. + R(). + SetContext(ctx). + SetResult(&result). + SetQueryParams(params.ParseToMap()). + Get(a.url.String()) + if err != nil { + return nil, fmt.Errorf("HTTP response failure: %w", err) + } + + return &result, nil +} + +func NewAPI(url *url.URL, httpClient *resty.Client) *API { + return &API{url: url.JoinPath(route), httpClient: httpClient} +} + +func HTTPErrorFormatter(action string, err error) *errutil.HTTPErrorFormatter { + return &errutil.HTTPErrorFormatter{ + Action: action, + API: "Admin User Paymails API", + Err: err, + } +} diff --git a/internal/api/v1/admin/paymails/paymails_api_test.go b/internal/api/v1/admin/paymails/paymails_api_test.go new file mode 100644 index 00000000..6c790e3a --- /dev/null +++ b/internal/api/v1/admin/paymails/paymails_api_test.go @@ -0,0 +1,177 @@ +package paymails_test + +import ( + "context" + "fmt" + "net/http" + "testing" + + "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/api/v1/admin/paymails/paymailstest" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/spvwallettest" + "github.com/bitcoin-sv/spv-wallet-go-client/queries" + "github.com/bitcoin-sv/spv-wallet/models/filter" + "github.com/bitcoin-sv/spv-wallet/models/response" + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/require" +) + +func TestPaymailsAPI_DeletePaymail(t *testing.T) { + id := "xpub22e6cba6-ef6e-432a-8612-63ac4b290ce9" + tests := map[string]struct { + responder httpmock.Responder + expectedResponse *response.PaymailAddress + expectedErr error + }{ + fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s response: 200", id): { + expectedResponse: paymailstest.ExpectedCreatedPaymail(t), + responder: httpmock.NewStringResponder(http.StatusOK, http.StatusText(http.StatusOK)), + }, + fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s response: 400", id): { + expectedErr: paymailstest.NewBadRequestSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewBadRequestSPVError()), + }, + fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s response: 500", id): { + expectedErr: paymailstest.NewInternalServerSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewInternalServerSPVError()), + }, + fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s str response: 500", id): { + expectedErr: errors.ErrUnrecognizedAPIResponse, + responder: httpmock.NewStringResponder(http.StatusInternalServerError, "unexpected internal server failure"), + }, + } + + url := spvwallettest.TestAPIAddr + "/api/v1/admin/paymails/" + id + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // when: + wallet, transport := spvwallettest.GivenSPVAdminAPI(t) + transport.RegisterResponder(http.MethodDelete, url, tc.responder) + + // then: + err := wallet.DeletePaymail(context.Background(), id) + require.ErrorIs(t, err, tc.expectedErr) + }) + } +} + +func TestPaymailsAPI_CreatePaymail(t *testing.T) { + tests := map[string]struct { + responder httpmock.Responder + expectedResponse *response.PaymailAddress + expectedErr error + }{ + "HTTP POST /api/v1/admin/paymails response: 200": { + expectedResponse: paymailstest.ExpectedCreatedPaymail(t), + responder: httpmock.NewJsonResponderOrPanic(http.StatusOK, httpmock.File("paymailstest/post_paymail_200.json")), + }, + "HTTP POST /api/v1/admin/paymails response: 400": { + expectedErr: paymailstest.NewBadRequestSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewBadRequestSPVError()), + }, + "HTTP POST /api/v1/admin/paymails response: 500": { + expectedErr: paymailstest.NewInternalServerSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewInternalServerSPVError()), + }, + "HTTP POST /api/v1/admin/paymails str response: 500": { + expectedErr: errors.ErrUnrecognizedAPIResponse, + responder: httpmock.NewStringResponder(http.StatusInternalServerError, "unexpected internal server failure"), + }, + } + + url := spvwallettest.TestAPIAddr + "/api/v1/admin/paymails" + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // when: + wallet, transport := spvwallettest.GivenSPVAdminAPI(t) + transport.RegisterResponder(http.MethodPost, url, tc.responder) + + // then: + got, err := wallet.CreatePaymail(context.Background(), &commands.CreatePaymail{ + Key: "xpub22e6cba6-ef6e-432a-8612-63ac4b290ce9", + }) + require.ErrorIs(t, err, tc.expectedErr) + require.EqualValues(t, tc.expectedResponse, got) + }) + } +} + +func TestPaymailsAPI_Paymails(t *testing.T) { + tests := map[string]struct { + responder httpmock.Responder + expectedResponse *queries.PaymailAddressPage + expectedErr error + }{ + "HTTP GET /api/v1/admin/paymails response: 200": { + expectedResponse: paymailstest.ExpectedPaymailsPage(t), + responder: httpmock.NewJsonResponderOrPanic(http.StatusOK, httpmock.File("paymailstest/get_paymails_200.json")), + }, + "HTTP GET /api/v1/admin/paymails response: 400": { + expectedErr: paymailstest.NewBadRequestSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewBadRequestSPVError()), + }, + "HTTP GET /api/v1/admin/paymails response: 500": { + expectedErr: paymailstest.NewInternalServerSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewInternalServerSPVError()), + }, + "HTTP GET /api/v1/admin/paymails str response: 500": { + expectedErr: errors.ErrUnrecognizedAPIResponse, + responder: httpmock.NewStringResponder(http.StatusInternalServerError, "unexpected internal server failure"), + }, + } + + url := spvwallettest.TestAPIAddr + "/api/v1/admin/paymails" + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // when: + wallet, transport := spvwallettest.GivenSPVAdminAPI(t) + transport.RegisterResponder(http.MethodGet, url, tc.responder) + + // then: + got, err := wallet.Paymails(context.Background(), queries.PaymailQueryWithPageFilter(filter.Page{Size: 1})) + require.ErrorIs(t, err, tc.expectedErr) + require.EqualValues(t, tc.expectedResponse, got) + }) + } +} + +func TestPaymailsAPI_Paymail(t *testing.T) { + id := "98dbafe0-4e2b-4307-8fbf-c55209214bae" + tests := map[string]struct { + responder httpmock.Responder + expectedResponse *response.PaymailAddress + expectedErr error + }{ + fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s response: 200", id): { + expectedResponse: paymailstest.ExpectedPaymail(t), + responder: httpmock.NewJsonResponderOrPanic(http.StatusOK, httpmock.File("paymailstest/get_paymail_200.json")), + }, + fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s response: 400", id): { + expectedErr: paymailstest.NewBadRequestSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewBadRequestSPVError()), + }, + fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s response: 500", id): { + expectedErr: paymailstest.NewInternalServerSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewInternalServerSPVError()), + }, + fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s str response: 500", id): { + expectedErr: errors.ErrUnrecognizedAPIResponse, + responder: httpmock.NewStringResponder(http.StatusInternalServerError, "unexpected internal server failure"), + }, + } + + url := spvwallettest.TestAPIAddr + "/api/v1/admin/paymails/" + id + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // when: + wallet, transport := spvwallettest.GivenSPVAdminAPI(t) + transport.RegisterResponder(http.MethodGet, url, tc.responder) + + // then: + got, err := wallet.Paymail(context.Background(), id) + require.ErrorIs(t, err, tc.expectedErr) + require.EqualValues(t, tc.expectedResponse, got) + }) + } +} diff --git a/internal/api/v1/admin/paymails/paymailstest/get_paymail_200.json b/internal/api/v1/admin/paymails/paymailstest/get_paymail_200.json new file mode 100644 index 00000000..7c15f641 --- /dev/null +++ b/internal/api/v1/admin/paymails/paymailstest/get_paymail_200.json @@ -0,0 +1,12 @@ +{ + "createdAt": "2024-10-02T10:28:15.544234Z", + "updatedAt": "2024-10-02T10:34:54.836433Z", + "deletedAt": null, + "metadata": null, + "id": "98dbafe0-4e2b-4307-8fbf-c55209214bae", + "xpubId": "0d71ac87-ef56-4b1a-8372-814481cface6", + "alias": "john.doe.test", + "domain": "john.doe.test.4chain.space", + "publicName": "john.doe.test", + "avatar": "http://localhost:3003/static/paymail/avatar.jpg" + } diff --git a/internal/api/v1/admin/paymails/paymailstest/get_paymails_200.json b/internal/api/v1/admin/paymails/paymailstest/get_paymails_200.json new file mode 100644 index 00000000..618bedd5 --- /dev/null +++ b/internal/api/v1/admin/paymails/paymailstest/get_paymails_200.json @@ -0,0 +1,34 @@ +{ + "content": [ + { + "createdAt": "2024-11-18T06:50:07.144902Z", + "updatedAt": "2024-11-18T06:50:07.144932Z", + "deletedAt": null, + "metadata": null, + "id": "31b80181-4d8b-4766-9bc7-76a1d9c6b44d", + "xpubId": "69245a3a-f9ed-4046-9acb-9d66c0b3750c", + "alias": "john.doe.test", + "domain": "john.doe.4chain.space", + "publicName": "John Doe", + "avatar": "" + }, + { + "createdAt": "2024-11-08T15:10:44.688653Z", + "updatedAt": "2024-11-18T07:19:51.561691Z", + "deletedAt": null, + "metadata": null, + "id": "ec91273e-9fb7-4f10-9ecb-d1848d238814", + "xpubId": "68026cb6-a549-45e8-97b1-11426bb16769", + "alias": "jane.doe.test", + "domain": "jane.doe.4chain.space", + "publicName": "Jane Doe", + "avatar": "http://localhost:3003/static/paymail/avatar.jpg" + } + ], + "page": { + "size": 10, + "number": 1, + "totalElements": 2, + "totalPages": 1 + } +} diff --git a/internal/api/v1/admin/paymails/paymailstest/paymail_api_fixtures.go b/internal/api/v1/admin/paymails/paymailstest/paymail_api_fixtures.go new file mode 100644 index 00000000..859ff444 --- /dev/null +++ b/internal/api/v1/admin/paymails/paymailstest/paymail_api_fixtures.go @@ -0,0 +1,105 @@ +package paymailstest + +import ( + "net/http" + "testing" + "time" + + "github.com/bitcoin-sv/spv-wallet-go-client/queries" + "github.com/bitcoin-sv/spv-wallet/models" + "github.com/bitcoin-sv/spv-wallet/models/response" +) + +func NewBadRequestSPVError() models.SPVError { + return models.SPVError{ + Message: http.StatusText(http.StatusBadRequest), + StatusCode: http.StatusBadRequest, + Code: "invalid-data-format", + } +} + +func NewInternalServerSPVError() models.SPVError { + return models.SPVError{ + Message: http.StatusText(http.StatusInternalServerError), + StatusCode: http.StatusInternalServerError, + Code: models.UnknownErrorCode, + } +} + +func Ptr[T any](value T) *T { + return &value +} + +func ExpectedCreatedPaymail(t *testing.T) *response.PaymailAddress { + return &response.PaymailAddress{ + Model: response.Model{ + CreatedAt: parseTime(t, "2024-12-02T10:22:45.263654Z"), + UpdatedAt: parseTime(t, "2024-12-02T11:22:45.263664+01:00"), + }, + ID: "069d0011-580e-4fc6-9f24-45471b732a8b", + XpubID: "22e6cba6-ef6e-432a-8612-63ac4b290ce9", + Alias: "john.doe.test", + Domain: "example.com", + PublicName: "john.doe.test", + Avatar: "", + } +} + +func ExpectedPaymail(t *testing.T) *response.PaymailAddress { + return &response.PaymailAddress{ + Model: response.Model{ + CreatedAt: parseTime(t, "2024-10-02T10:28:15.544234Z"), + UpdatedAt: parseTime(t, "2024-10-02T10:34:54.836433Z"), + }, + ID: "98dbafe0-4e2b-4307-8fbf-c55209214bae", + XpubID: "0d71ac87-ef56-4b1a-8372-814481cface6", + Alias: "john.doe.test", + Domain: "john.doe.test.4chain.space", + PublicName: "john.doe.test", + Avatar: "http://localhost:3003/static/paymail/avatar.jpg", + } +} + +func ExpectedPaymailsPage(t *testing.T) *queries.PaymailAddressPage { + return &queries.PaymailAddressPage{ + Content: []*response.PaymailAddress{ + { + Model: response.Model{ + CreatedAt: parseTime(t, "2024-11-18T06:50:07.144902Z"), + UpdatedAt: parseTime(t, "2024-11-18T06:50:07.144932Z"), + }, + ID: "31b80181-4d8b-4766-9bc7-76a1d9c6b44d", + XpubID: "69245a3a-f9ed-4046-9acb-9d66c0b3750c", + Alias: "john.doe.test", + Domain: "john.doe.4chain.space", + PublicName: "John Doe", + }, + { + Model: response.Model{ + CreatedAt: parseTime(t, "2024-11-08T15:10:44.688653Z"), + UpdatedAt: parseTime(t, "2024-11-18T07:19:51.561691Z"), + }, + ID: "ec91273e-9fb7-4f10-9ecb-d1848d238814", + XpubID: "68026cb6-a549-45e8-97b1-11426bb16769", + Alias: "jane.doe.test", + Domain: "jane.doe.4chain.space", + PublicName: "Jane Doe", + Avatar: "http://localhost:3003/static/paymail/avatar.jpg", + }, + }, + Page: response.PageDescription{ + Size: 10, + Number: 1, + TotalElements: 2, + TotalPages: 1, + }, + } +} + +func parseTime(t *testing.T, s string) time.Time { + ts, err := time.Parse(time.RFC3339Nano, s) + if err != nil { + t.Fatalf("test helper - time parse: %s", err) + } + return ts +} diff --git a/internal/api/v1/admin/paymails/paymailstest/post_paymail_200.json b/internal/api/v1/admin/paymails/paymailstest/post_paymail_200.json new file mode 100644 index 00000000..4560247f --- /dev/null +++ b/internal/api/v1/admin/paymails/paymailstest/post_paymail_200.json @@ -0,0 +1,12 @@ +{ + "createdAt": "2024-12-02T10:22:45.263654Z", + "updatedAt": "2024-12-02T11:22:45.263664+01:00", + "deletedAt": null, + "metadata": null, + "id": "069d0011-580e-4fc6-9f24-45471b732a8b", + "xpubId": "22e6cba6-ef6e-432a-8612-63ac4b290ce9", + "alias": "john.doe.test", + "domain": "example.com", + "publicName": "john.doe.test", + "avatar": "" + } diff --git a/queries/paymails.go b/queries/paymails.go new file mode 100644 index 00000000..b09edeba --- /dev/null +++ b/queries/paymails.go @@ -0,0 +1,46 @@ +package queries + +import ( + "github.com/bitcoin-sv/spv-wallet/models/filter" + "github.com/bitcoin-sv/spv-wallet/models/response" +) + +// PaymailAddressPage is an alias for the paymail addresses response page model returned by the SPV Wallet API. +// It provides a paginated list of paymails along with pagination metadata. +type PaymailAddressPage = response.PageModel[response.PaymailAddress] + +// PaymailQuery aggregates query parameters for constructing the paymail addresses endpoint URL. +// It contains filters for metadata, pagination, and user paymails-specific attributes. +type PaymailQuery struct { + Metadata map[string]any // Metadata filters for refining the search. + PageFilter filter.Page // Pagination details, including page number, size, and sorting. + PaymailFilter filter.AdminPaymailFilter // Filters for paymail attributes (ID, xPubID, alias, domain, public name). +} + +// PaymailQueryOption defines a functional option for configuring a ContactQuery instance. +// These options allow flexible setup of filters and pagination for the query. +type PaymailQueryOption func(*PaymailQuery) + +// PaymailQueryWithMetadataFilter adds metadata filters to the paymail addresses search URL. +// The provided metadata attributes are appended as query parameters. +func PaymailQueryWithMetadataFilter(m map[string]any) PaymailQueryOption { + return func(pq *PaymailQuery) { + pq.Metadata = m + } +} + +// PaymailQueryWithPageFilter adds pagination filters, like page number, size, and sorting options, +// to the paymail addresses search URL as query parameters. +func PaymailQueryWithPageFilter(f filter.Page) PaymailQueryOption { + return func(pq *PaymailQuery) { + pq.PageFilter = f + } +} + +// PaymailQueryWithPaymailFilter adds filters for paymail address attributes like ID, xPubID, alias, domain, public name. +// These filters are appended as query parameters to the paymail addresses search URL. +func PaymailQueryWithPaymailFilter(pf filter.AdminPaymailFilter) PaymailQueryOption { + return func(pq *PaymailQuery) { + pq.PaymailFilter = pf + } +} From 197833d7873f4e6f8703f17aa8e52a77884d231b Mon Sep 17 00:00:00 2001 From: mgosek-4chain Date: Tue, 3 Dec 2024 06:58:43 +0100 Subject: [PATCH 2/6] refactor(SPV-1231): replace using random xPub keys in paymail API examples. --- .../create_paymail_as_admin/create_paymail_as_admin.go | 9 ++------- .../delete_paymail_as_admin/delete_paymail_as_admin.go | 9 ++------- .../fetch_paymail_as_admin/fetch_paymail_as_admin.go | 10 ++-------- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/examples/create_paymail_as_admin/create_paymail_as_admin.go b/examples/create_paymail_as_admin/create_paymail_as_admin.go index f015b531..564caefa 100644 --- a/examples/create_paymail_as_admin/create_paymail_as_admin.go +++ b/examples/create_paymail_as_admin/create_paymail_as_admin.go @@ -9,15 +9,10 @@ import ( "github.com/bitcoin-sv/spv-wallet-go-client/examples" "github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" - "github.com/bitcoin-sv/spv-wallet-go-client/walletkeys" ) func main() { ctx := context.Background() - keys, err := walletkeys.RandomKeys() - if err != nil { - log.Fatal(err) - } adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv) if err != nil { @@ -26,7 +21,7 @@ func main() { xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{ Metadata: map[string]any{"xpub_key": "xpub_val"}, - XPub: keys.XPub(), + XPub: examples.XPub, }) if err != nil { log.Fatal(err) @@ -35,7 +30,7 @@ func main() { addr := exampleutil.RandomPaymail() paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{ - Key: keys.XPub(), + Key: examples.XPub, Address: addr, Metadata: querybuilders.Metadata{"key": "value"}, }) diff --git a/examples/delete_paymail_as_admin/delete_paymail_as_admin.go b/examples/delete_paymail_as_admin/delete_paymail_as_admin.go index 2b0bd508..34b66678 100644 --- a/examples/delete_paymail_as_admin/delete_paymail_as_admin.go +++ b/examples/delete_paymail_as_admin/delete_paymail_as_admin.go @@ -10,15 +10,10 @@ import ( "github.com/bitcoin-sv/spv-wallet-go-client/examples" "github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" - "github.com/bitcoin-sv/spv-wallet-go-client/walletkeys" ) func main() { ctx := context.Background() - keys, err := walletkeys.RandomKeys() - if err != nil { - log.Fatal(err) - } adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv) if err != nil { @@ -27,7 +22,7 @@ func main() { xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{ Metadata: map[string]any{"xpub_key": "xpub_val"}, - XPub: keys.XPub(), + XPub: examples.XPub, }) if err != nil { log.Fatal(err) @@ -36,7 +31,7 @@ func main() { addr := exampleutil.RandomPaymail() paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{ - Key: keys.XPub(), + Key: examples.XPub, Address: addr, Metadata: querybuilders.Metadata{"key": "value"}, }) diff --git a/examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go b/examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go index 0e06b8e1..42afd8da 100644 --- a/examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go +++ b/examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go @@ -9,16 +9,10 @@ import ( "github.com/bitcoin-sv/spv-wallet-go-client/examples" "github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" - "github.com/bitcoin-sv/spv-wallet-go-client/walletkeys" ) func main() { ctx := context.Background() - keys, err := walletkeys.RandomKeys() - if err != nil { - log.Fatal(err) - } - adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv) if err != nil { log.Fatal(err) @@ -26,7 +20,7 @@ func main() { xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{ Metadata: map[string]any{"xpub_key": "xpub_val"}, - XPub: keys.XPub(), + XPub: examples.XPub, }) if err != nil { log.Fatal(err) @@ -34,7 +28,7 @@ func main() { exampleutil.Print("[HTTP POST][Step 1] Create xPub - api/v1/admin/users", xPub) paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{ - Key: keys.XPub(), + Key: examples.XPub, Address: exampleutil.RandomPaymail(), Metadata: querybuilders.Metadata{"key": "value"}, }) From eca5dad36ae2cae80cb15363f07da55f13f70cd6 Mon Sep 17 00:00:00 2001 From: mgosek-4chain Date: Thu, 5 Dec 2024 13:14:41 +0100 Subject: [PATCH 3/6] refactor(SPV-1231): remove empty line in the struct. --- admin_api.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/admin_api.go b/admin_api.go index cfbdaa5a..369afe91 100644 --- a/admin_api.go +++ b/admin_api.go @@ -29,9 +29,8 @@ import ( // Methods may return wrapped errors, including models.SPVError or // ErrUnrecognizedAPIResponse, depending on the behavior of the SPV Wallet API. type AdminAPI struct { - xpubsAPI *xpubs.API - paymailsAPI *paymails.API - + xpubsAPI *xpubs.API + paymailsAPI *paymails.API transactionsAPI *transactions.API contactsAPI *contacts.API invitationsAPI *invitations.API From 4f02689ca1614c7dd79fd4d439f3ae48482a5c8f Mon Sep 17 00:00:00 2001 From: mgosek-4chain Date: Thu, 5 Dec 2024 13:20:18 +0100 Subject: [PATCH 4/6] refactor(SPV-1231): remove redundant test helper functions from packages. --- .../paymails/paymail_filter_builder_test.go | 21 ++++---- .../v1/admin/paymails/paymails_api_test.go | 32 ++++++------- .../paymailstest/paymail_api_fixtures.go | 48 ++++--------------- .../querybuilders/extended_url_values_test.go | 26 +++++----- .../model_filter_builder_test.go | 30 ++++++------ .../v1/querybuilders/query_builder_test.go | 12 ++--- .../querybuilderstest/querybuilderstest.go | 18 ------- 7 files changed, 71 insertions(+), 116 deletions(-) delete mode 100644 internal/api/v1/querybuilders/querybuilderstest/querybuilderstest.go diff --git a/internal/api/v1/admin/paymails/paymail_filter_builder_test.go b/internal/api/v1/admin/paymails/paymail_filter_builder_test.go index beb86c7d..3b7069fc 100644 --- a/internal/api/v1/admin/paymails/paymail_filter_builder_test.go +++ b/internal/api/v1/admin/paymails/paymail_filter_builder_test.go @@ -4,7 +4,6 @@ import ( "net/url" "testing" - "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/paymails/paymailstest" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" "github.com/bitcoin-sv/spv-wallet/models/filter" "github.com/stretchr/testify/require" @@ -21,7 +20,7 @@ func TestPaymailFilterBuilder_Build(t *testing.T) { }, "admin paymail filter: filter with only 'id' field set": { filter: filter.AdminPaymailFilter{ - ID: paymailstest.Ptr("b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"), + ID: ptr("b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"), }, expectedParams: url.Values{ "id": []string{"b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"}, @@ -29,7 +28,7 @@ func TestPaymailFilterBuilder_Build(t *testing.T) { }, "admin paymail filter: filter with only 'xPubId' field set": { filter: filter.AdminPaymailFilter{ - XpubID: paymailstest.Ptr("7d373830-1d74-4c4b-a435-04ce09398027"), + XpubID: ptr("7d373830-1d74-4c4b-a435-04ce09398027"), }, expectedParams: url.Values{ "xpubId": []string{"7d373830-1d74-4c4b-a435-04ce09398027"}, @@ -37,7 +36,7 @@ func TestPaymailFilterBuilder_Build(t *testing.T) { }, "admin paymail filter: filter with only 'alias' field set": { filter: filter.AdminPaymailFilter{ - Alias: paymailstest.Ptr("alias"), + Alias: ptr("alias"), }, expectedParams: url.Values{ "alias": []string{"alias"}, @@ -45,7 +44,7 @@ func TestPaymailFilterBuilder_Build(t *testing.T) { }, "admin paymail filter: filter with only 'public name' field set": { filter: filter.AdminPaymailFilter{ - PublicName: paymailstest.Ptr("Alice"), + PublicName: ptr("Alice"), }, expectedParams: url.Values{ "publicName": []string{"Alice"}, @@ -53,10 +52,10 @@ func TestPaymailFilterBuilder_Build(t *testing.T) { }, "admin paymail filter: all fields set": { filter: filter.AdminPaymailFilter{ - ID: paymailstest.Ptr("b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"), - XpubID: paymailstest.Ptr("7d373830-1d74-4c4b-a435-04ce09398027"), - PublicName: paymailstest.Ptr("Alice"), - Alias: paymailstest.Ptr("alias"), + ID: ptr("b950f5de-3d3a-40b6-bdf8-c9d60e9e0a0a"), + XpubID: ptr("7d373830-1d74-4c4b-a435-04ce09398027"), + PublicName: ptr("Alice"), + Alias: ptr("alias"), }, expectedParams: url.Values{ "publicName": []string{"Alice"}, @@ -82,3 +81,7 @@ func TestPaymailFilterBuilder_Build(t *testing.T) { }) } } + +func ptr[T any](value T) *T { + return &value +} diff --git a/internal/api/v1/admin/paymails/paymails_api_test.go b/internal/api/v1/admin/paymails/paymails_api_test.go index 6c790e3a..528c13d0 100644 --- a/internal/api/v1/admin/paymails/paymails_api_test.go +++ b/internal/api/v1/admin/paymails/paymails_api_test.go @@ -29,12 +29,12 @@ func TestPaymailsAPI_DeletePaymail(t *testing.T) { responder: httpmock.NewStringResponder(http.StatusOK, http.StatusText(http.StatusOK)), }, fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s response: 400", id): { - expectedErr: paymailstest.NewBadRequestSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewBadRequestSPVError()), + expectedErr: spvwallettest.NewBadRequestSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewBadRequestSPVError()), }, fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s response: 500", id): { - expectedErr: paymailstest.NewInternalServerSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewInternalServerSPVError()), + expectedErr: spvwallettest.NewInternalServerSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewInternalServerSPVError()), }, fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s str response: 500", id): { expectedErr: errors.ErrUnrecognizedAPIResponse, @@ -67,12 +67,12 @@ func TestPaymailsAPI_CreatePaymail(t *testing.T) { responder: httpmock.NewJsonResponderOrPanic(http.StatusOK, httpmock.File("paymailstest/post_paymail_200.json")), }, "HTTP POST /api/v1/admin/paymails response: 400": { - expectedErr: paymailstest.NewBadRequestSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewBadRequestSPVError()), + expectedErr: spvwallettest.NewBadRequestSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewBadRequestSPVError()), }, "HTTP POST /api/v1/admin/paymails response: 500": { - expectedErr: paymailstest.NewInternalServerSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewInternalServerSPVError()), + expectedErr: spvwallettest.NewInternalServerSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewInternalServerSPVError()), }, "HTTP POST /api/v1/admin/paymails str response: 500": { expectedErr: errors.ErrUnrecognizedAPIResponse, @@ -108,12 +108,12 @@ func TestPaymailsAPI_Paymails(t *testing.T) { responder: httpmock.NewJsonResponderOrPanic(http.StatusOK, httpmock.File("paymailstest/get_paymails_200.json")), }, "HTTP GET /api/v1/admin/paymails response: 400": { - expectedErr: paymailstest.NewBadRequestSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewBadRequestSPVError()), + expectedErr: spvwallettest.NewBadRequestSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewBadRequestSPVError()), }, "HTTP GET /api/v1/admin/paymails response: 500": { - expectedErr: paymailstest.NewInternalServerSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewInternalServerSPVError()), + expectedErr: spvwallettest.NewInternalServerSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewInternalServerSPVError()), }, "HTTP GET /api/v1/admin/paymails str response: 500": { expectedErr: errors.ErrUnrecognizedAPIResponse, @@ -148,12 +148,12 @@ func TestPaymailsAPI_Paymail(t *testing.T) { responder: httpmock.NewJsonResponderOrPanic(http.StatusOK, httpmock.File("paymailstest/get_paymail_200.json")), }, fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s response: 400", id): { - expectedErr: paymailstest.NewBadRequestSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewBadRequestSPVError()), + expectedErr: spvwallettest.NewBadRequestSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewBadRequestSPVError()), }, fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s response: 500", id): { - expectedErr: paymailstest.NewInternalServerSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, paymailstest.NewInternalServerSPVError()), + expectedErr: spvwallettest.NewInternalServerSPVError(), + responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewInternalServerSPVError()), }, fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s str response: 500", id): { expectedErr: errors.ErrUnrecognizedAPIResponse, diff --git a/internal/api/v1/admin/paymails/paymailstest/paymail_api_fixtures.go b/internal/api/v1/admin/paymails/paymailstest/paymail_api_fixtures.go index 859ff444..6583fbe6 100644 --- a/internal/api/v1/admin/paymails/paymailstest/paymail_api_fixtures.go +++ b/internal/api/v1/admin/paymails/paymailstest/paymail_api_fixtures.go @@ -1,40 +1,18 @@ package paymailstest import ( - "net/http" "testing" - "time" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/spvwallettest" "github.com/bitcoin-sv/spv-wallet-go-client/queries" - "github.com/bitcoin-sv/spv-wallet/models" "github.com/bitcoin-sv/spv-wallet/models/response" ) -func NewBadRequestSPVError() models.SPVError { - return models.SPVError{ - Message: http.StatusText(http.StatusBadRequest), - StatusCode: http.StatusBadRequest, - Code: "invalid-data-format", - } -} - -func NewInternalServerSPVError() models.SPVError { - return models.SPVError{ - Message: http.StatusText(http.StatusInternalServerError), - StatusCode: http.StatusInternalServerError, - Code: models.UnknownErrorCode, - } -} - -func Ptr[T any](value T) *T { - return &value -} - func ExpectedCreatedPaymail(t *testing.T) *response.PaymailAddress { return &response.PaymailAddress{ Model: response.Model{ - CreatedAt: parseTime(t, "2024-12-02T10:22:45.263654Z"), - UpdatedAt: parseTime(t, "2024-12-02T11:22:45.263664+01:00"), + CreatedAt: spvwallettest.ParseTime(t, "2024-12-02T10:22:45.263654Z"), + UpdatedAt: spvwallettest.ParseTime(t, "2024-12-02T11:22:45.263664+01:00"), }, ID: "069d0011-580e-4fc6-9f24-45471b732a8b", XpubID: "22e6cba6-ef6e-432a-8612-63ac4b290ce9", @@ -48,8 +26,8 @@ func ExpectedCreatedPaymail(t *testing.T) *response.PaymailAddress { func ExpectedPaymail(t *testing.T) *response.PaymailAddress { return &response.PaymailAddress{ Model: response.Model{ - CreatedAt: parseTime(t, "2024-10-02T10:28:15.544234Z"), - UpdatedAt: parseTime(t, "2024-10-02T10:34:54.836433Z"), + CreatedAt: spvwallettest.ParseTime(t, "2024-10-02T10:28:15.544234Z"), + UpdatedAt: spvwallettest.ParseTime(t, "2024-10-02T10:34:54.836433Z"), }, ID: "98dbafe0-4e2b-4307-8fbf-c55209214bae", XpubID: "0d71ac87-ef56-4b1a-8372-814481cface6", @@ -65,8 +43,8 @@ func ExpectedPaymailsPage(t *testing.T) *queries.PaymailAddressPage { Content: []*response.PaymailAddress{ { Model: response.Model{ - CreatedAt: parseTime(t, "2024-11-18T06:50:07.144902Z"), - UpdatedAt: parseTime(t, "2024-11-18T06:50:07.144932Z"), + CreatedAt: spvwallettest.ParseTime(t, "2024-11-18T06:50:07.144902Z"), + UpdatedAt: spvwallettest.ParseTime(t, "2024-11-18T06:50:07.144932Z"), }, ID: "31b80181-4d8b-4766-9bc7-76a1d9c6b44d", XpubID: "69245a3a-f9ed-4046-9acb-9d66c0b3750c", @@ -76,8 +54,8 @@ func ExpectedPaymailsPage(t *testing.T) *queries.PaymailAddressPage { }, { Model: response.Model{ - CreatedAt: parseTime(t, "2024-11-08T15:10:44.688653Z"), - UpdatedAt: parseTime(t, "2024-11-18T07:19:51.561691Z"), + CreatedAt: spvwallettest.ParseTime(t, "2024-11-08T15:10:44.688653Z"), + UpdatedAt: spvwallettest.ParseTime(t, "2024-11-18T07:19:51.561691Z"), }, ID: "ec91273e-9fb7-4f10-9ecb-d1848d238814", XpubID: "68026cb6-a549-45e8-97b1-11426bb16769", @@ -95,11 +73,3 @@ func ExpectedPaymailsPage(t *testing.T) *queries.PaymailAddressPage { }, } } - -func parseTime(t *testing.T, s string) time.Time { - ts, err := time.Parse(time.RFC3339Nano, s) - if err != nil { - t.Fatalf("test helper - time parse: %s", err) - } - return ts -} diff --git a/internal/api/v1/querybuilders/extended_url_values_test.go b/internal/api/v1/querybuilders/extended_url_values_test.go index 96c3feba..fdb06ab4 100644 --- a/internal/api/v1/querybuilders/extended_url_values_test.go +++ b/internal/api/v1/querybuilders/extended_url_values_test.go @@ -6,15 +6,15 @@ import ( "time" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" - "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders/querybuilderstest" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/spvwallettest" "github.com/bitcoin-sv/spv-wallet/models/filter" "github.com/stretchr/testify/require" ) func TestExtendedURLValues_AddPair(t *testing.T) { // given: - to := querybuilderstest.ParseTime(t, "2024-10-07T14:03:26.736816Z") - from := querybuilderstest.ParseTime(t, "2024-10-07T14:03:26.736816Z") + to := spvwallettest.ParseTime(t, "2024-10-07T14:03:26.736816Z") + from := spvwallettest.ParseTime(t, "2024-10-07T14:03:26.736816Z") expectedValues := url.Values{ "key1": []string{"str"}, "key2": []string{"1"}, @@ -30,10 +30,10 @@ func TestExtendedURLValues_AddPair(t *testing.T) { params := querybuilders.NewExtendedURLValues() params.AddPair("key1", "str") params.AddPair("key2", 1) - params.AddPair("key3", querybuilderstest.Ptr("str_ptr")) - params.AddPair("key4", querybuilderstest.Ptr(uint64(64))) - params.AddPair("key5", querybuilderstest.Ptr(uint32(32))) - params.AddPair("key6", querybuilderstest.Ptr(bool(false))) + params.AddPair("key3", spvwallettest.Ptr("str_ptr")) + params.AddPair("key4", spvwallettest.Ptr(uint64(64))) + params.AddPair("key5", spvwallettest.Ptr(uint32(32))) + params.AddPair("key6", spvwallettest.Ptr(bool(false))) params.AddPair("key7", &filter.TimeRange{ From: &from, To: &to, @@ -45,8 +45,8 @@ func TestExtendedURLValues_AddPair(t *testing.T) { func TestExtendedURLValues_ParseToMap(t *testing.T) { // given: - to := querybuilderstest.ParseTime(t, "2024-10-07T14:03:26.736816Z") - from := querybuilderstest.ParseTime(t, "2024-10-07T14:03:26.736816Z") + to := spvwallettest.ParseTime(t, "2024-10-07T14:03:26.736816Z") + from := spvwallettest.ParseTime(t, "2024-10-07T14:03:26.736816Z") expectedValues := map[string]string{ "key1": "str", "key2": "1", @@ -61,10 +61,10 @@ func TestExtendedURLValues_ParseToMap(t *testing.T) { params := querybuilders.NewExtendedURLValues() params.AddPair("key1", "str") params.AddPair("key2", 1) - params.AddPair("key3", querybuilderstest.Ptr("str_ptr")) - params.AddPair("key4", querybuilderstest.Ptr(uint64(64))) - params.AddPair("key5", querybuilderstest.Ptr(uint32(32))) - params.AddPair("key6", querybuilderstest.Ptr(bool(false))) + params.AddPair("key3", spvwallettest.Ptr("str_ptr")) + params.AddPair("key4", spvwallettest.Ptr(uint64(64))) + params.AddPair("key5", spvwallettest.Ptr(uint32(32))) + params.AddPair("key6", spvwallettest.Ptr(bool(false))) params.AddPair("key7", &filter.TimeRange{ From: &from, To: &to, diff --git a/internal/api/v1/querybuilders/model_filter_builder_test.go b/internal/api/v1/querybuilders/model_filter_builder_test.go index 45d6c36e..8b22efdc 100644 --- a/internal/api/v1/querybuilders/model_filter_builder_test.go +++ b/internal/api/v1/querybuilders/model_filter_builder_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" - "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders/querybuilderstest" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/spvwallettest" "github.com/bitcoin-sv/spv-wallet/models/filter" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestModelFilterQueryBuilder_Build(t *testing.T) { "includeDeleted": []string{"true"}, }, filter: filter.ModelFilter{ - IncludeDeleted: querybuilderstest.Ptr(true), + IncludeDeleted: spvwallettest.Ptr(true), }, }, "model filter: filter with only created range 'from' field set": { @@ -31,7 +31,7 @@ func TestModelFilterQueryBuilder_Build(t *testing.T) { }, filter: filter.ModelFilter{ CreatedRange: &filter.TimeRange{ - From: querybuilderstest.Ptr(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), + From: spvwallettest.Ptr(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), }, }, }, @@ -41,7 +41,7 @@ func TestModelFilterQueryBuilder_Build(t *testing.T) { }, filter: filter.ModelFilter{ CreatedRange: &filter.TimeRange{ - To: querybuilderstest.Ptr(time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC)), + To: spvwallettest.Ptr(time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC)), }, }, }, @@ -52,8 +52,8 @@ func TestModelFilterQueryBuilder_Build(t *testing.T) { }, filter: filter.ModelFilter{ CreatedRange: &filter.TimeRange{ - From: querybuilderstest.Ptr(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), - To: querybuilderstest.Ptr(time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC)), + From: spvwallettest.Ptr(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), + To: spvwallettest.Ptr(time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC)), }, }, }, @@ -63,7 +63,7 @@ func TestModelFilterQueryBuilder_Build(t *testing.T) { }, filter: filter.ModelFilter{ UpdatedRange: &filter.TimeRange{ - From: querybuilderstest.Ptr(time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)), + From: spvwallettest.Ptr(time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)), }, }, }, @@ -73,7 +73,7 @@ func TestModelFilterQueryBuilder_Build(t *testing.T) { }, filter: filter.ModelFilter{ UpdatedRange: &filter.TimeRange{ - To: querybuilderstest.Ptr(time.Date(2021, 2, 2, 0, 0, 0, 0, time.UTC)), + To: spvwallettest.Ptr(time.Date(2021, 2, 2, 0, 0, 0, 0, time.UTC)), }, }, }, @@ -84,8 +84,8 @@ func TestModelFilterQueryBuilder_Build(t *testing.T) { }, filter: filter.ModelFilter{ UpdatedRange: &filter.TimeRange{ - From: querybuilderstest.Ptr(time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)), - To: querybuilderstest.Ptr(time.Date(2021, 2, 2, 0, 0, 0, 0, time.UTC)), + From: spvwallettest.Ptr(time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)), + To: spvwallettest.Ptr(time.Date(2021, 2, 2, 0, 0, 0, 0, time.UTC)), }, }, }, @@ -98,14 +98,14 @@ func TestModelFilterQueryBuilder_Build(t *testing.T) { "updatedRange[to]": []string{"2021-02-02T00:00:00Z"}, }, filter: filter.ModelFilter{ - IncludeDeleted: querybuilderstest.Ptr(true), + IncludeDeleted: spvwallettest.Ptr(true), CreatedRange: &filter.TimeRange{ - From: querybuilderstest.Ptr(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), - To: querybuilderstest.Ptr(time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC)), + From: spvwallettest.Ptr(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), + To: spvwallettest.Ptr(time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC)), }, UpdatedRange: &filter.TimeRange{ - From: querybuilderstest.Ptr(time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)), - To: querybuilderstest.Ptr(time.Date(2021, 2, 2, 0, 0, 0, 0, time.UTC)), + From: spvwallettest.Ptr(time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)), + To: spvwallettest.Ptr(time.Date(2021, 2, 2, 0, 0, 0, 0, time.UTC)), }, }, }, diff --git a/internal/api/v1/querybuilders/query_builder_test.go b/internal/api/v1/querybuilders/query_builder_test.go index 9d13dad4..da6c901c 100644 --- a/internal/api/v1/querybuilders/query_builder_test.go +++ b/internal/api/v1/querybuilders/query_builder_test.go @@ -8,7 +8,7 @@ import ( goclienterr "github.com/bitcoin-sv/spv-wallet-go-client/errors" "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders" - "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders/querybuilderstest" + "github.com/bitcoin-sv/spv-wallet-go-client/internal/spvwallettest" "github.com/bitcoin-sv/spv-wallet/models/filter" "github.com/stretchr/testify/require" ) @@ -66,14 +66,14 @@ func TestQueryBuilder_Build(t *testing.T) { SortBy: "id", }, ModelFilter: filter.ModelFilter{ - IncludeDeleted: querybuilderstest.Ptr(true), + IncludeDeleted: spvwallettest.Ptr(true), CreatedRange: &filter.TimeRange{ - From: querybuilderstest.Ptr(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), - To: querybuilderstest.Ptr(time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC)), + From: spvwallettest.Ptr(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), + To: spvwallettest.Ptr(time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC)), }, UpdatedRange: &filter.TimeRange{ - From: querybuilderstest.Ptr(time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)), - To: querybuilderstest.Ptr(time.Date(2021, 2, 2, 0, 0, 0, 0, time.UTC)), + From: spvwallettest.Ptr(time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC)), + To: spvwallettest.Ptr(time.Date(2021, 2, 2, 0, 0, 0, 0, time.UTC)), }, }, MetadataFilter: querybuilders.Metadata{ diff --git a/internal/api/v1/querybuilders/querybuilderstest/querybuilderstest.go b/internal/api/v1/querybuilders/querybuilderstest/querybuilderstest.go deleted file mode 100644 index c24928e4..00000000 --- a/internal/api/v1/querybuilders/querybuilderstest/querybuilderstest.go +++ /dev/null @@ -1,18 +0,0 @@ -package querybuilderstest - -import ( - "testing" - "time" -) - -func ParseTime(t *testing.T, s string) time.Time { - ts, err := time.Parse(time.RFC3339Nano, s) - if err != nil { - t.Fatalf("test helper - time parse: %s", err) - } - return ts -} - -func Ptr[T any](value T) *T { - return &value -} From b4b45df6f59f6b1015c64c0776ce958ad2465e45 Mon Sep 17 00:00:00 2001 From: mgosek-4chain Date: Thu, 5 Dec 2024 13:28:38 +0100 Subject: [PATCH 5/6] refactor(SPV-1231): group consts in paymails admin API. --- internal/api/v1/admin/paymails/paymails_api.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/api/v1/admin/paymails/paymails_api.go b/internal/api/v1/admin/paymails/paymails_api.go index 494641b5..477a3c9b 100644 --- a/internal/api/v1/admin/paymails/paymails_api.go +++ b/internal/api/v1/admin/paymails/paymails_api.go @@ -13,7 +13,10 @@ import ( "github.com/go-resty/resty/v2" ) -const route = "api/v1/admin/paymails" +const ( + route = "api/v1/admin/paymails" + api = "Admin User Paymails API" +) type API struct { httpClient *resty.Client @@ -106,7 +109,7 @@ func NewAPI(url *url.URL, httpClient *resty.Client) *API { func HTTPErrorFormatter(action string, err error) *errutil.HTTPErrorFormatter { return &errutil.HTTPErrorFormatter{ Action: action, - API: "Admin User Paymails API", + API: api, Err: err, } } From 7510288ac6574a01ba0aebc6337a94dec6a3b45d Mon Sep 17 00:00:00 2001 From: mgosek-4chain Date: Fri, 6 Dec 2024 10:32:48 +0100 Subject: [PATCH 6/6] refactor(SPV-1231): fix responders HTTP status codes in paymails API unit tests. --- internal/api/v1/admin/paymails/paymails_api_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/api/v1/admin/paymails/paymails_api_test.go b/internal/api/v1/admin/paymails/paymails_api_test.go index 528c13d0..1161cb3e 100644 --- a/internal/api/v1/admin/paymails/paymails_api_test.go +++ b/internal/api/v1/admin/paymails/paymails_api_test.go @@ -34,7 +34,7 @@ func TestPaymailsAPI_DeletePaymail(t *testing.T) { }, fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s response: 500", id): { expectedErr: spvwallettest.NewInternalServerSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewInternalServerSPVError()), + responder: httpmock.NewJsonResponderOrPanic(http.StatusInternalServerError, spvwallettest.NewInternalServerSPVError()), }, fmt.Sprintf("HTTP DELETE /api/v1/admin/paymails/%s str response: 500", id): { expectedErr: errors.ErrUnrecognizedAPIResponse, @@ -72,7 +72,7 @@ func TestPaymailsAPI_CreatePaymail(t *testing.T) { }, "HTTP POST /api/v1/admin/paymails response: 500": { expectedErr: spvwallettest.NewInternalServerSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewInternalServerSPVError()), + responder: httpmock.NewJsonResponderOrPanic(http.StatusInternalServerError, spvwallettest.NewInternalServerSPVError()), }, "HTTP POST /api/v1/admin/paymails str response: 500": { expectedErr: errors.ErrUnrecognizedAPIResponse, @@ -113,7 +113,7 @@ func TestPaymailsAPI_Paymails(t *testing.T) { }, "HTTP GET /api/v1/admin/paymails response: 500": { expectedErr: spvwallettest.NewInternalServerSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewInternalServerSPVError()), + responder: httpmock.NewJsonResponderOrPanic(http.StatusInternalServerError, spvwallettest.NewInternalServerSPVError()), }, "HTTP GET /api/v1/admin/paymails str response: 500": { expectedErr: errors.ErrUnrecognizedAPIResponse, @@ -153,7 +153,7 @@ func TestPaymailsAPI_Paymail(t *testing.T) { }, fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s response: 500", id): { expectedErr: spvwallettest.NewInternalServerSPVError(), - responder: httpmock.NewJsonResponderOrPanic(http.StatusBadRequest, spvwallettest.NewInternalServerSPVError()), + responder: httpmock.NewJsonResponderOrPanic(http.StatusInternalServerError, spvwallettest.NewInternalServerSPVError()), }, fmt.Sprintf("HTTP GET /api/v1/admin/paymails/%s str response: 500", id): { expectedErr: errors.ErrUnrecognizedAPIResponse,