diff --git a/account_betas.go b/account_betas.go new file mode 100644 index 000000000..0897a4a73 --- /dev/null +++ b/account_betas.go @@ -0,0 +1,114 @@ +package linodego + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "time" + + "github.com/go-resty/resty/v2" + "github.com/linode/linodego/internal/parseabletime" +) + +// The details and enrollment information of a Beta program that an account is enrolled in. +type AccountBetaProgram struct { + Label string `json:"label"` + ID string `json:"id"` + Description string `json:"description"` + Started *time.Time `json:"-"` + Ended *time.Time `json:"-"` + + // Date the account was enrolled in the beta program + Enrolled *time.Time `json:"-"` +} + +// AccountBetaProgramCreateOpts fields are those accepted by JoinBetaProgram +type AccountBetaProgramCreateOpts struct { + ID string `json:"id"` +} + +// AccountBetasPagedResponse represents a paginated Account Beta Programs API response +type AccountBetasPagedResponse struct { + *PageOptions + Data []AccountBetaProgram `json:"data"` +} + +// endpoint gets the endpoint URL for AccountBetaProgram +func (AccountBetasPagedResponse) endpoint(_ ...any) string { + return "/account/betas" +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (cBeta *AccountBetaProgram) UnmarshalJSON(b []byte) error { + type Mask AccountBetaProgram + + p := struct { + *Mask + Started *parseabletime.ParseableTime `json:"started"` + Ended *parseabletime.ParseableTime `json:"ended"` + Enrolled *parseabletime.ParseableTime `json:"enrolled"` + }{ + Mask: (*Mask)(cBeta), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + cBeta.Started = (*time.Time)(p.Started) + cBeta.Ended = (*time.Time)(p.Ended) + cBeta.Enrolled = (*time.Time)(p.Enrolled) + + return nil +} + +func (resp *AccountBetasPagedResponse) castResult(r *resty.Request, e string) (int, int, error) { + res, err := coupleAPIErrors(r.SetResult(AccountBetasPagedResponse{}).Get(e)) + if err != nil { + return 0, 0, err + } + castedRes := res.Result().(*AccountBetasPagedResponse) + resp.Data = append(resp.Data, castedRes.Data...) + return castedRes.Pages, castedRes.Results, nil +} + +// ListAccountBetaPrograms lists all beta programs an account is enrolled in. +func (c *Client) ListAccountBetaPrograms(ctx context.Context, opts *ListOptions) ([]AccountBetaProgram, error) { + response := AccountBetasPagedResponse{} + err := c.listHelper(ctx, &response, opts) + if err != nil { + return nil, err + } + return response.Data, nil +} + +// GetAccountBetaProgram gets the details of a beta program an account is enrolled in. +func (c *Client) GetAccountBetaProgram(ctx context.Context, betaID string) (*AccountBetaProgram, error) { + req := c.R(ctx).SetResult(&AccountBetaProgram{}) + betaID = url.PathEscape(betaID) + b := fmt.Sprintf("/account/betas/%s", betaID) + r, err := coupleAPIErrors(req.Get(b)) + if err != nil { + return nil, err + } + + return r.Result().(*AccountBetaProgram), nil +} + +// JoinBetaProgram enrolls an account into a beta program. +func (c *Client) JoinBetaProgram(ctx context.Context, opts AccountBetaProgramCreateOpts) (*AccountBetaProgram, error) { + body, err := json.Marshal(opts) + if err != nil { + return nil, err + } + + e := "account/betas" + req := c.R(ctx).SetResult(&AccountBetaProgram{}).SetBody(string(body)) + r, err := coupleAPIErrors(req.Post(e)) + if err != nil { + return nil, err + } + + return r.Result().(*AccountBetaProgram), nil +} diff --git a/test/integration/account_betas_test.go b/test/integration/account_betas_test.go new file mode 100644 index 000000000..270c86c50 --- /dev/null +++ b/test/integration/account_betas_test.go @@ -0,0 +1,50 @@ +package integration + +import ( + "context" + "testing" + + "github.com/linode/linodego" +) + +func TestAccountBetaPrograms_List(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestAccountBetaPrograms_List") + defer teardown() + + betas, err := client.ListAccountBetaPrograms(context.Background(), &linodego.ListOptions{}) + if err != nil { + t.Errorf("Error getting Account Beta programs, expected struct, got error %v", err) + } + + if len(betas) == 0 { + t.Errorf("Expected to see account beta program returned.") + } else { + assertDateSet(t, betas[0].Enrolled) + } +} + +func TestAccountBetaProgram_Get(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestAccountBetaProgram_Get") + defer teardown() + + betaID := "cool-beta" + + // Enroll the account into a beta program. + createOpts := linodego.AccountBetaProgramCreateOpts{ID: betaID} + + _, err := client.JoinBetaProgram(context.Background(), createOpts) + if err != nil { + t.Errorf("Error joining a Beta program, expected struct, got error %v", err) + } + + beta, err := client.GetAccountBetaProgram(context.Background(), betaID) + + if err != nil { + t.Errorf("Error getting an Account Beta program, expected struct, got error %v", err) + } + + if beta.ID != betaID { + t.Errorf("expected beta ID to be %s; got %s", betaID, beta.ID) + } + +} diff --git a/test/integration/fixtures/TestAccountBetaProgram_Get.yaml b/test/integration/fixtures/TestAccountBetaProgram_Get.yaml new file mode 100644 index 000000000..2d4cbf101 --- /dev/null +++ b/test/integration/fixtures/TestAccountBetaProgram_Get.yaml @@ -0,0 +1,139 @@ +--- +version: 1 +interactions: +- request: + body: '{"id":"cool-beta"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/account/betas + method: POST + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - account:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1200" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/account/betas/cool-beta + method: GET + response: + body: '{"id": "cool-beta", "label": "\r\n\r\nRepellat consequatur sunt qui. Fugit + eligendi ipsa et assumenda ea aspernatur esse. A itaque iste distinctio qui + voluptas eum enim ipsa. Labore ducimus sit voluptas expedita ut non.\r\n\r\nAtque + iusto est cupiditate dignissimos soluta facere sunt molestias. Tenetur labore + est est et repudiandae praesentium officiis quis. Eveniet voluptate dignissimos + laboriosam esse maiores inventore reiciendis explicabo. Voluptas perspiciatis + voluptatibus distinctio.\r\n\r\nQui sed esse iusto ipsa repudiandae id. Quo + aut omnis id tenetur odio recusandae delectus iste. Dicta exercitationem voluptatem + accusamus. Voluptatum ut nesciunt architecto maiores.\r\n\r\nRecusandae natus + rerum aut quos aliquam. Et quisquam minima earum. Quam et quod nisi est praesentium + fuga voluptas. Blanditiis veniam aut totam.\r\n\r\nAssumenda accusantium similique + non reprehenderit sint deserunt harum vero. Et qui nihil ut. Reprehenderit quam + dicta qui repellendus perspiciatis voluptatum.\r\n", "enrolled": "2018-01-02T03:04:05", + "description": "\r\n\r\nRepellat consequatur sunt qui. Fugit eligendi ipsa et + assumenda ea aspernatur esse. A itaque iste distinctio qui voluptas eum enim + ipsa. Labore ducimus sit voluptas expedita ut non.\r\n\r\nAtque iusto est cupiditate + dignissimos soluta facere sunt molestias. Tenetur labore est est et repudiandae + praesentium officiis quis. Eveniet voluptate dignissimos laboriosam esse maiores + inventore reiciendis explicabo. Voluptas perspiciatis voluptatibus distinctio.\r\n\r\nQui + sed esse iusto ipsa repudiandae id. Quo aut omnis id tenetur odio recusandae + delectus iste. Dicta exercitationem voluptatem accusamus. Voluptatum ut nesciunt + architecto maiores.\r\n\r\nRecusandae natus rerum aut quos aliquam. Et quisquam + minima earum. Quam et quod nisi est praesentium fuga voluptas. Blanditiis veniam + aut totam.\r\n\r\nAssumenda accusantium similique non reprehenderit sint deserunt + harum vero. Et qui nihil ut. Reprehenderit quam dicta qui repellendus perspiciatis + voluptatum.\r\n", "started": "2018-01-02T03:04:05", "ended": "2018-01-02T03:04:05"}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=0, s-maxage=0, no-cache, no-store + - private, max-age=60, s-maxage=60 + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - account:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1200" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/integration/fixtures/TestAccountBetaPrograms_List.yaml b/test/integration/fixtures/TestAccountBetaPrograms_List.yaml new file mode 100644 index 000000000..89493d6e2 --- /dev/null +++ b/test/integration/fixtures/TestAccountBetaPrograms_List.yaml @@ -0,0 +1,85 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/account/betas + method: GET + response: + body: '{"data": [{"id": "cool-beta", "label": "\r\n\r\nRepellat consequatur sunt + qui. Fugit eligendi ipsa et assumenda ea aspernatur esse. A itaque iste distinctio + qui voluptas eum enim ipsa. Labore ducimus sit voluptas expedita ut non.\r\n\r\nAtque + iusto est cupiditate dignissimos soluta facere sunt molestias. Tenetur labore + est est et repudiandae praesentium officiis quis. Eveniet voluptate dignissimos + laboriosam esse maiores inventore reiciendis explicabo. Voluptas perspiciatis + voluptatibus distinctio.\r\n\r\nQui sed esse iusto ipsa repudiandae id. Quo + aut omnis id tenetur odio recusandae delectus iste. Dicta exercitationem voluptatem + accusamus. Voluptatum ut nesciunt architecto maiores.\r\n\r\nRecusandae natus + rerum aut quos aliquam. Et quisquam minima earum. Quam et quod nisi est praesentium + fuga voluptas. Blanditiis veniam aut totam.\r\n\r\nAssumenda accusantium similique + non reprehenderit sint deserunt harum vero. Et qui nihil ut. Reprehenderit quam + dicta qui repellendus perspiciatis voluptatum.\r\n", "enrolled": "2018-01-02T03:04:05", + "description": "\r\n\r\nRepellat consequatur sunt qui. Fugit eligendi ipsa et + assumenda ea aspernatur esse. A itaque iste distinctio qui voluptas eum enim + ipsa. Labore ducimus sit voluptas expedita ut non.\r\n\r\nAtque iusto est cupiditate + dignissimos soluta facere sunt molestias. Tenetur labore est est et repudiandae + praesentium officiis quis. Eveniet voluptate dignissimos laboriosam esse maiores + inventore reiciendis explicabo. Voluptas perspiciatis voluptatibus distinctio.\r\n\r\nQui + sed esse iusto ipsa repudiandae id. Quo aut omnis id tenetur odio recusandae + delectus iste. Dicta exercitationem voluptatem accusamus. Voluptatum ut nesciunt + architecto maiores.\r\n\r\nRecusandae natus rerum aut quos aliquam. Et quisquam + minima earum. Quam et quod nisi est praesentium fuga voluptas. Blanditiis veniam + aut totam.\r\n\r\nAssumenda accusantium similique non reprehenderit sint deserunt + harum vero. Et qui nihil ut. Reprehenderit quam dicta qui repellendus perspiciatis + voluptatum.\r\n", "started": "2018-01-02T03:04:05", "ended": "2018-01-02T03:04:05"}], + "page": 1, "pages": 1, "results": 1}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=0, s-maxage=0, no-cache, no-store + - private, max-age=60, s-maxage=60 + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - account:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1200" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: ""