From 433772dc5d3cfda19c6b18d390486e15fa253473 Mon Sep 17 00:00:00 2001 From: ezilber-akamai Date: Mon, 25 Nov 2024 14:39:05 -0500 Subject: [PATCH] Added support for service transfers --- account_service_transfer.go | 100 ++++++++++++++++ test/unit/account_service_transfer_test.go | 111 ++++++++++++++++++ .../account_service_transfers_get.json | 14 +++ .../account_service_transfers_list.json | 21 ++++ .../account_service_transfers_request.json | 14 +++ 5 files changed, 260 insertions(+) create mode 100644 account_service_transfer.go create mode 100644 test/unit/account_service_transfer_test.go create mode 100644 test/unit/fixtures/account_service_transfers_get.json create mode 100644 test/unit/fixtures/account_service_transfers_list.json create mode 100644 test/unit/fixtures/account_service_transfers_request.json diff --git a/account_service_transfer.go b/account_service_transfer.go new file mode 100644 index 000000000..045f8453d --- /dev/null +++ b/account_service_transfer.go @@ -0,0 +1,100 @@ +package linodego + +import ( + "context" + "encoding/json" + "github.com/linode/linodego/internal/parseabletime" + "time" +) + +// AccountServiceTransferStatus constants start with AccountServiceTransfer and +// include Linode API Account Service Transfer Status values. +type AccountServiceTransferStatus string + +// AccountServiceTransferStatus constants reflect the current status of an AccountServiceTransfer +const ( + AccountServiceTransferAccepted AccountServiceTransferStatus = "accepted" + AccountServiceTransferCanceled AccountServiceTransferStatus = "canceled" + AccountServiceTransferCompleted AccountServiceTransferStatus = "completed" + AccountServiceTransferFailed AccountServiceTransferStatus = "failed" + AccountServiceTransferPending AccountServiceTransferStatus = "pending" + AccountServiceTransferStale AccountServiceTransferStatus = "stale" +) + +// AccountServiceTransfer represents a request to transfer a service on an Account +type AccountServiceTransfer struct { + Created *time.Time `json:"-"` + Entities AccountServiceTransferEntity `json:"entities"` + Expiry *time.Time `json:"-"` + IsSender bool `json:"is_sender"` + Status AccountServiceTransferStatus `json:"status"` + Token string `json:"token"` + Updated *time.Time `json:"-"` +} + +// AccountServiceTransferEntity represents a collection of the services to include +// in a transfer request, separated by type. +// Note: At this time, only Linodes can be transferred. +type AccountServiceTransferEntity struct { + Linodes []int `json:"linodes"` +} + +type AccountServiceTransferRequestOptions struct { + Entities AccountServiceTransferEntity `json:"entities"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (ast *AccountServiceTransfer) UnmarshalJSON(b []byte) error { + type Mask AccountServiceTransfer + + p := struct { + *Mask + Created *parseabletime.ParseableTime `json:"created"` + Expiry *parseabletime.ParseableTime `json:"expiry"` + Updated *parseabletime.ParseableTime `json:"updated"` + }{ + Mask: (*Mask)(ast), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + ast.Created = (*time.Time)(p.Created) + ast.Expiry = (*time.Time)(p.Expiry) + ast.Updated = (*time.Time)(p.Updated) + + return nil +} + +// ListAccountServiceTransfer gets a paginated list of AccountServiceTransfer for the Account. +func (c *Client) ListAccountServiceTransfer(ctx context.Context, opts *ListOptions) ([]AccountServiceTransfer, error) { + e := "account/service-transfers" + return getPaginatedResults[AccountServiceTransfer](ctx, c, e, opts) +} + +// GetAccountServiceTransfer gets the details of the AccountServiceTransfer for the provided token. +func (c *Client) GetAccountServiceTransfer(ctx context.Context, token string) (*AccountServiceTransfer, error) { + e := formatAPIPath("account/service-transfers/%s", token) + return doGETRequest[AccountServiceTransfer](ctx, c, e) +} + +// RequestAccountServiceTransfer creates a transfer request for the specified services. +func (c *Client) RequestAccountServiceTransfer(ctx context.Context, opts AccountServiceTransferRequestOptions) (*AccountServiceTransfer, error) { + e := "account/service-transfers" + return doPOSTRequest[AccountServiceTransfer](ctx, c, e, opts) +} + +// AcceptAccountServiceTransfer accepts an AccountServiceTransfer for the provided token to +// receive the services included in the transfer to the Account. +func (c *Client) AcceptAccountServiceTransfer(ctx context.Context, token string) error { + e := formatAPIPath("account/service-transfers/%s/accept", token) + _, err := doPOSTRequest[AccountServiceTransfer, any](ctx, c, e) + return err +} + +// CancelAccountServiceTransfer cancels the AccountServiceTransfer for the provided token. +func (c *Client) CancelAccountServiceTransfer(ctx context.Context, token string) error { + e := formatAPIPath("account/service-transfers/%s", token) + return doDELETERequest(ctx, c, e) +} diff --git a/test/unit/account_service_transfer_test.go b/test/unit/account_service_transfer_test.go new file mode 100644 index 000000000..1b7b9f30b --- /dev/null +++ b/test/unit/account_service_transfer_test.go @@ -0,0 +1,111 @@ +package unit + +import ( + "context" + "github.com/jarcoal/httpmock" + "github.com/linode/linodego" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestAccountServiceTransfer_List(t *testing.T) { + fixtureData, err := fixtures.GetFixture("account_service_transfers_list") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("account/service-transfers", fixtureData) + + transfers, err := base.Client.ListAccountServiceTransfer(context.Background(), nil) + assert.NoError(t, err) + + assert.Equal(t, 1, len(transfers)) + ast := transfers[0] + assert.Equal(t, time.Time(time.Date(2021, time.February, 11, 16, 37, 3, 0, time.UTC)), *ast.Created) + assert.Equal(t, time.Time(time.Date(2021, time.February, 12, 16, 37, 3, 0, time.UTC)), *ast.Expiry) + assert.Equal(t, time.Time(time.Date(2021, time.February, 11, 16, 37, 3, 0, time.UTC)), *ast.Updated) + assert.Equal(t, 111, ast.Entities.Linodes[0]) + assert.Equal(t, 222, ast.Entities.Linodes[1]) + assert.Equal(t, true, ast.IsSender) + assert.Equal(t, linodego.AccountServiceTransferStatus("pending"), ast.Status) + assert.Equal(t, "123E4567-E89B-12D3-A456-426614174000", ast.Token) +} + +func TestAccountServiceTransfer_Get(t *testing.T) { + fixtureData, err := fixtures.GetFixture("account_service_transfers_get") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("account/service-transfers/123E4567-E89B-12D3-A456-426614174000", fixtureData) + + ast, err := base.Client.GetAccountServiceTransfer(context.Background(), "123E4567-E89B-12D3-A456-426614174000") + assert.NoError(t, err) + + assert.Equal(t, time.Time(time.Date(2021, time.February, 11, 16, 37, 3, 0, time.UTC)), *ast.Created) + assert.Equal(t, time.Time(time.Date(2021, time.February, 12, 16, 37, 3, 0, time.UTC)), *ast.Expiry) + assert.Equal(t, time.Time(time.Date(2021, time.February, 11, 16, 37, 3, 0, time.UTC)), *ast.Updated) + assert.Equal(t, 111, ast.Entities.Linodes[0]) + assert.Equal(t, 222, ast.Entities.Linodes[1]) + assert.Equal(t, true, ast.IsSender) + assert.Equal(t, linodego.AccountServiceTransferStatus("pending"), ast.Status) + assert.Equal(t, "123E4567-E89B-12D3-A456-426614174000", ast.Token) +} + +func TestAccountServiceTransfer_Request(t *testing.T) { + fixtureData, err := fixtures.GetFixture("account_service_transfers_request") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.AccountServiceTransferRequestOptions{ + Entities: linodego.AccountServiceTransferEntity{ + Linodes: []int{111, 222}, + }, + } + + base.MockPost("account/service-transfers", fixtureData) + + ast, err := base.Client.RequestAccountServiceTransfer(context.Background(), requestData) + assert.NoError(t, err) + + assert.Equal(t, time.Time(time.Date(2021, time.February, 11, 16, 37, 3, 0, time.UTC)), *ast.Created) + assert.Equal(t, time.Time(time.Date(2021, time.February, 12, 16, 37, 3, 0, time.UTC)), *ast.Expiry) + assert.Equal(t, time.Time(time.Date(2021, time.February, 11, 16, 37, 3, 0, time.UTC)), *ast.Updated) + assert.Equal(t, 111, ast.Entities.Linodes[0]) + assert.Equal(t, 222, ast.Entities.Linodes[1]) + assert.Equal(t, true, ast.IsSender) + assert.Equal(t, linodego.AccountServiceTransferStatus("pending"), ast.Status) + assert.Equal(t, "123E4567-E89B-12D3-A456-426614174000", ast.Token) +} + +func TestAccountServiceTransfer_Accept(t *testing.T) { + client := createMockClient(t) + + httpmock.RegisterRegexpResponder("POST", + mockRequestURL(t, "account/service-transfers/123E4567-E89B-12D3-A456-426614174000/accept"), + httpmock.NewStringResponder(200, "{}")) + + if err := client.AcceptAccountServiceTransfer(context.Background(), "123E4567-E89B-12D3-A456-426614174000"); err != nil { + t.Fatal(err) + } +} + +func TestAccountServiceTransfer_Cancel(t *testing.T) { + client := createMockClient(t) + + httpmock.RegisterRegexpResponder("DELETE", + mockRequestURL(t, "account/service-transfers/123E4567-E89B-12D3-A456-426614174000"), + httpmock.NewStringResponder(200, "{}")) + + if err := client.CancelAccountServiceTransfer(context.Background(), "123E4567-E89B-12D3-A456-426614174000"); err != nil { + t.Fatal(err) + } +} diff --git a/test/unit/fixtures/account_service_transfers_get.json b/test/unit/fixtures/account_service_transfers_get.json new file mode 100644 index 000000000..1074b7775 --- /dev/null +++ b/test/unit/fixtures/account_service_transfers_get.json @@ -0,0 +1,14 @@ +{ + "created": "2021-02-11T16:37:03", + "entities": { + "linodes": [ + 111, + 222 + ] + }, + "expiry": "2021-02-12T16:37:03", + "is_sender": true, + "status": "pending", + "token": "123E4567-E89B-12D3-A456-426614174000", + "updated": "2021-02-11T16:37:03" +} \ No newline at end of file diff --git a/test/unit/fixtures/account_service_transfers_list.json b/test/unit/fixtures/account_service_transfers_list.json new file mode 100644 index 000000000..46aa85914 --- /dev/null +++ b/test/unit/fixtures/account_service_transfers_list.json @@ -0,0 +1,21 @@ +{ + "data": [ + { + "created": "2021-02-11T16:37:03", + "entities": { + "linodes": [ + 111, + 222 + ] + }, + "expiry": "2021-02-12T16:37:03", + "is_sender": true, + "status": "pending", + "token": "123E4567-E89B-12D3-A456-426614174000", + "updated": "2021-02-11T16:37:03" + } + ], + "page": 1, + "pages": 1, + "results": 1 +} \ No newline at end of file diff --git a/test/unit/fixtures/account_service_transfers_request.json b/test/unit/fixtures/account_service_transfers_request.json new file mode 100644 index 000000000..1074b7775 --- /dev/null +++ b/test/unit/fixtures/account_service_transfers_request.json @@ -0,0 +1,14 @@ +{ + "created": "2021-02-11T16:37:03", + "entities": { + "linodes": [ + 111, + 222 + ] + }, + "expiry": "2021-02-12T16:37:03", + "is_sender": true, + "status": "pending", + "token": "123E4567-E89B-12D3-A456-426614174000", + "updated": "2021-02-11T16:37:03" +} \ No newline at end of file