diff --git a/fastly/errors.go b/fastly/errors.go index 5c89173fb..d1ce9e08e 100644 --- a/fastly/errors.go +++ b/fastly/errors.go @@ -157,6 +157,10 @@ var ErrMissingMonth = NewFieldError("Month") // requires a "Name" key, but one was not set. var ErrMissingName = NewFieldError("Name") +// ErrMissingResourceID is an error that is returned when an input struct +// requires a "ResourceID" key, but one was not set. +var ErrMissingResourceID = NewFieldError("ResourceID") + // ErrMissingNameValue is an error that is returned when an input struct // requires a "Name" key, but one was not set. var ErrMissingNameValue = NewFieldError("Name").Message("service name can't be an empty value") diff --git a/fastly/fixtures/resources/cleanup-object-store.yaml b/fastly/fixtures/resources/cleanup-object-store.yaml new file mode 100644 index 000000000..c30ac63e9 --- /dev/null +++ b/fastly/fixtures/resources/cleanup-object-store.yaml @@ -0,0 +1,33 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/resources/stores/object/2dem3bjbpie7k1vyy70fh5 + method: DELETE + response: + body: "" + headers: + Accept-Ranges: + - bytes + Date: + - Thu, 19 Jan 2023 11:28:16 GMT + Strict-Transport-Security: + - max-age=31536000 + Via: + - 1.1 varnish + X-Cache: + - MISS + X-Cache-Hits: + - "0" + X-Served-By: + - cache-fty21339-FTY, cache-man4131-MAN + X-Timer: + - S1674127696.810292,VS0,VE563 + status: 204 No Content + code: 204 + duration: "" diff --git a/fastly/fixtures/resources/cleanup.yaml b/fastly/fixtures/resources/cleanup.yaml new file mode 100644 index 000000000..86aa8deb4 --- /dev/null +++ b/fastly/fixtures/resources/cleanup.yaml @@ -0,0 +1,47 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/service/7i6HN3TK9wS159v2gPAZ8A/version/5/resource/rn6c7YSyRxBJMejpyvoQa1 + method: DELETE + response: + body: '{"msg":"Record not found","detail":"Couldn''t find Link ''{ deleted =\u003e + 0000-00-00 00:00:00, id =\u003e rn6c7YSyRxBJMejpyvoQa1, service =\u003e 7i6HN3TK9wS159v2gPAZ8A, + version =\u003e 5 }''"}' + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-store + Content-Type: + - application/json + Date: + - Thu, 19 Jan 2023 11:28:15 GMT + Fastly-Ratelimit-Remaining: + - "9990" + Fastly-Ratelimit-Reset: + - "1674129600" + Status: + - 404 Not Found + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + Via: + - 1.1 varnish, 1.1 varnish + X-Cache: + - MISS, MISS + X-Cache-Hits: + - 0, 0 + X-Served-By: + - cache-control-cp-aws-us-east-1-prod-5-CONTROL-AWS, cache-man4131-MAN + X-Timer: + - S1674127695.461054,VS0,VE322 + status: 404 Not Found + code: 404 + duration: "" diff --git a/fastly/fixtures/resources/create-object-store.yaml b/fastly/fixtures/resources/create-object-store.yaml new file mode 100644 index 000000000..af70db94b --- /dev/null +++ b/fastly/fixtures/resources/create-object-store.yaml @@ -0,0 +1,39 @@ +--- +version: 1 +interactions: +- request: + body: '{"name":"test-object-store"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/resources/stores/object + method: POST + response: + body: '{"id":"2dem3bjbpie7k1vyy70fh5","name":"test-object-store","created_at":"2023-01-19T11:28:13.754Z","updated_at":"2023-01-19T11:28:13.754Z"}' + headers: + Accept-Ranges: + - bytes + Content-Type: + - application/json + Date: + - Thu, 19 Jan 2023 11:28:13 GMT + Strict-Transport-Security: + - max-age=31536000 + Via: + - 1.1 varnish + X-Cache: + - MISS + X-Cache-Hits: + - "0" + X-Served-By: + - cache-fty21339-FTY, cache-man4131-MAN + X-Timer: + - S1674127692.140623,VS0,VE1665 + status: 201 Created + code: 201 + duration: "" diff --git a/fastly/fixtures/resources/create.yaml b/fastly/fixtures/resources/create.yaml new file mode 100644 index 000000000..b35d38978 --- /dev/null +++ b/fastly/fixtures/resources/create.yaml @@ -0,0 +1,51 @@ +--- +version: 1 +interactions: +- request: + body: name=test-object-store-name-for-linking&resource_id=2dem3bjbpie7k1vyy70fh5 + form: + name: + - test-object-store-name-for-linking + resource_id: + - 2dem3bjbpie7k1vyy70fh5 + headers: + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/service/7i6HN3TK9wS159v2gPAZ8A/version/5/resource + method: POST + response: + body: '{"id":"rn6c7YSyRxBJMejpyvoQa1","name":"test-object-store-name-for-linking","service_id":"7i6HN3TK9wS159v2gPAZ8A","version":5,"created_at":"2023-01-19T11:28:14Z","updated_at":"2023-01-19T11:28:14Z","deleted_at":null,"resource_id":"2dem3bjbpie7k1vyy70fh5","resource_type":"object-store","href":null}' + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-store + Content-Type: + - application/json + Date: + - Thu, 19 Jan 2023 11:28:14 GMT + Fastly-Ratelimit-Remaining: + - "9993" + Fastly-Ratelimit-Reset: + - "1674129600" + Status: + - 200 OK + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + Via: + - 1.1 varnish, 1.1 varnish + X-Cache: + - MISS, MISS + X-Cache-Hits: + - 0, 0 + X-Served-By: + - cache-control-cp-aws-us-east-1-prod-3-CONTROL-AWS, cache-man4131-MAN + X-Timer: + - S1674127694.832397,VS0,VE381 + status: 200 OK + code: 200 + duration: "" diff --git a/fastly/fixtures/resources/delete.yaml b/fastly/fixtures/resources/delete.yaml new file mode 100644 index 000000000..df958f529 --- /dev/null +++ b/fastly/fixtures/resources/delete.yaml @@ -0,0 +1,45 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/service/7i6HN3TK9wS159v2gPAZ8A/version/5/resource/rn6c7YSyRxBJMejpyvoQa1 + method: DELETE + response: + body: '{"status":"ok"}' + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-store + Content-Type: + - application/json + Date: + - Thu, 19 Jan 2023 11:28:15 GMT + Fastly-Ratelimit-Remaining: + - "9991" + Fastly-Ratelimit-Reset: + - "1674129600" + Status: + - 200 OK + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + Via: + - 1.1 varnish, 1.1 varnish + X-Cache: + - MISS, MISS + X-Cache-Hits: + - 0, 0 + X-Served-By: + - cache-control-cp-aws-us-east-1-prod-5-CONTROL-AWS, cache-man4131-MAN + X-Timer: + - S1674127695.215445,VS0,VE220 + status: 200 OK + code: 200 + duration: "" diff --git a/fastly/fixtures/resources/get.yaml b/fastly/fixtures/resources/get.yaml new file mode 100644 index 000000000..aafdd3425 --- /dev/null +++ b/fastly/fixtures/resources/get.yaml @@ -0,0 +1,41 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/service/7i6HN3TK9wS159v2gPAZ8A/version/5/resource/rn6c7YSyRxBJMejpyvoQa1 + method: GET + response: + body: '{"id":"rn6c7YSyRxBJMejpyvoQa1","name":"test-object-store-name-for-linking","service_id":"7i6HN3TK9wS159v2gPAZ8A","version":5,"created_at":"2023-01-19T11:28:14Z","updated_at":"2023-01-19T11:28:14Z","deleted_at":null,"resource_id":"2dem3bjbpie7k1vyy70fh5","resource_type":"object-store","href":null}' + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-store + Content-Type: + - application/json + Date: + - Thu, 19 Jan 2023 11:28:14 GMT + Status: + - 200 OK + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + Via: + - 1.1 varnish, 1.1 varnish + X-Cache: + - MISS, MISS + X-Cache-Hits: + - 0, 0 + X-Served-By: + - cache-control-cp-aws-us-east-1-prod-3-CONTROL-AWS, cache-man4131-MAN + X-Timer: + - S1674127695.594823,VS0,VE341 + status: 200 OK + code: 200 + duration: "" diff --git a/fastly/fixtures/resources/list.yaml b/fastly/fixtures/resources/list.yaml new file mode 100644 index 000000000..1eedc2277 --- /dev/null +++ b/fastly/fixtures/resources/list.yaml @@ -0,0 +1,41 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/service/7i6HN3TK9wS159v2gPAZ8A/version/5/resource + method: GET + response: + body: '[{"id":"rn6c7YSyRxBJMejpyvoQa1","name":"test-object-store-name-for-linking","service_id":"7i6HN3TK9wS159v2gPAZ8A","version":5,"created_at":"2023-01-19T11:28:14Z","updated_at":"2023-01-19T11:28:14Z","deleted_at":null,"resource_id":"2dem3bjbpie7k1vyy70fh5","resource_type":"object-store","href":null}]' + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-store + Content-Type: + - application/json + Date: + - Thu, 19 Jan 2023 11:28:14 GMT + Status: + - 200 OK + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + Via: + - 1.1 varnish, 1.1 varnish + X-Cache: + - MISS, MISS + X-Cache-Hits: + - 0, 0 + X-Served-By: + - cache-control-cp-aws-us-east-1-prod-1-CONTROL-AWS, cache-man4131-MAN + X-Timer: + - S1674127694.241459,VS0,VE328 + status: 200 OK + code: 200 + duration: "" diff --git a/fastly/fixtures/resources/update.yaml b/fastly/fixtures/resources/update.yaml new file mode 100644 index 000000000..c05eec533 --- /dev/null +++ b/fastly/fixtures/resources/update.yaml @@ -0,0 +1,49 @@ +--- +version: 1 +interactions: +- request: + body: name=new-object-store-alias-for-my-service + form: + name: + - new-object-store-alias-for-my-service + headers: + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/service/7i6HN3TK9wS159v2gPAZ8A/version/5/resource/rn6c7YSyRxBJMejpyvoQa1 + method: PUT + response: + body: '{"id":"rn6c7YSyRxBJMejpyvoQa1","name":"new-object-store-alias-for-my-service","service_id":"7i6HN3TK9wS159v2gPAZ8A","version":5,"created_at":"2023-01-19T11:28:14Z","updated_at":"2023-01-19T11:28:14Z","deleted_at":null,"resource_id":"2dem3bjbpie7k1vyy70fh5","resource_type":"object-store","href":null}' + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-store + Content-Type: + - application/json + Date: + - Thu, 19 Jan 2023 11:28:15 GMT + Fastly-Ratelimit-Remaining: + - "9992" + Fastly-Ratelimit-Reset: + - "1674129600" + Status: + - 200 OK + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + Via: + - 1.1 varnish, 1.1 varnish + X-Cache: + - MISS, MISS + X-Cache-Hits: + - 0, 0 + X-Served-By: + - cache-control-cp-aws-us-east-1-prod-1-CONTROL-AWS, cache-man4131-MAN + X-Timer: + - S1674127695.965974,VS0,VE222 + status: 200 OK + code: 200 + duration: "" diff --git a/fastly/fixtures/resources/version.yaml b/fastly/fixtures/resources/version.yaml new file mode 100644 index 000000000..77eb96c93 --- /dev/null +++ b/fastly/fixtures/resources/version.yaml @@ -0,0 +1,47 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - FastlyGo/7.0.0 (+github.com/fastly/go-fastly; go1.16.15) + url: https://api.fastly.com/service/7i6HN3TK9wS159v2gPAZ8A/version + method: POST + response: + body: '{"service_id":"7i6HN3TK9wS159v2gPAZ8A","number":5}' + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-store + Content-Type: + - application/json + Date: + - Thu, 19 Jan 2023 11:28:12 GMT + Fastly-Ratelimit-Remaining: + - "9994" + Fastly-Ratelimit-Reset: + - "1674129600" + Status: + - 200 OK + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Accept-Encoding + Via: + - 1.1 varnish, 1.1 varnish + X-Cache: + - MISS, MISS + X-Cache-Hits: + - 0, 0 + X-Served-By: + - cache-control-cp-aws-us-east-1-prod-5-CONTROL-AWS, cache-man4131-MAN + X-Timer: + - S1674127692.739443,VS0,VE375 + status: 200 OK + code: 200 + duration: "" diff --git a/fastly/resource.go b/fastly/resource.go new file mode 100644 index 000000000..44f979a83 --- /dev/null +++ b/fastly/resource.go @@ -0,0 +1,242 @@ +package fastly + +import ( + "fmt" + "net/url" + "sort" + "time" +) + +// Resource represents a response from the Fastly API. +type Resource struct { + // CreatedAt is the date and time in ISO 8601 format. + CreatedAt *time.Time `mapstructure:"created_at"` + // CreatedAt is the date and time in ISO 8601 format. + DeletedAt *time.Time `mapstructure:"deleted_at"` + // HREF is the path to the resource. + HREF string `mapstructure:"href"` + // ID is an alphanumeric string identifying the resource. + ID string `mapstructure:"id"` + // Name is the name of the resource being linked to. + Name string `mapstructure:"name"` + // ResourceID is the ID of the linked resource. + ResourceID string `mapstructure:"resource_id"` + // ResourceType is a resource type. + ResourceType string `mapstructure:"resource_type"` + // ServiceID is an alphanumeric string identifying the service. + ServiceID string `mapstructure:"service_id"` + // ServiceVersion is an integer identifying a service version. + ServiceVersion string `mapstructure:"version"` + // UpdatedAt is the date and time in ISO 8601 format. + UpdatedAt *time.Time `mapstructure:"updated_at"` +} + +// resourcesByName is a sortable list of resources. +type resourcesByName []*Resource + +// Len, Swap, and Less implement the sortable interface. +func (s resourcesByName) Len() int { + return len(s) +} + +func (s resourcesByName) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s resourcesByName) Less(i, j int) bool { + return s[i].Name < s[j].Name +} + +// ListResourcesInput is used as input to the ListResources function. +type ListResourcesInput struct { + // ServiceID is the ID of the service (required). + ServiceID string + // ServiceVersion is the specific configuration version (required). + ServiceVersion int +} + +// ListResources retrieves all resources. +func (c *Client) ListResources(i *ListResourcesInput) ([]*Resource, error) { + if i.ServiceID == "" { + return nil, ErrMissingServiceID + } + if i.ServiceVersion == 0 { + return nil, ErrMissingServiceVersion + } + + path := fmt.Sprintf("/service/%s/version/%d/resource", i.ServiceID, i.ServiceVersion) + resp, err := c.Get(path, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var rs []*Resource + if err := decodeBodyMap(resp.Body, &rs); err != nil { + return nil, err + } + sort.Stable(resourcesByName(rs)) + return rs, nil +} + +// CreateResourceInput is used as input to the CreateResource function. +type CreateResourceInput struct { + // Name is the name of the resource being linked to (e.g. an object store). + // + // NOTE: This doesn't have to match the actual object-store name. + // This is an opportunity for you to use an 'alias' for your object store. + // So your service will now refer to the object-store using this name. + Name *string `url:"name,omitempty"` + // ResourceID is the ID of the linked resource. + ResourceID *string `url:"resource_id,omitempty"` + // ServiceID is the ID of the service (required). + ServiceID string `url:"-"` + // ServiceVersion is the specific configuration version (required). + ServiceVersion int `url:"-"` +} + +// CreateResource creates a new resource. +func (c *Client) CreateResource(i *CreateResourceInput) (*Resource, error) { + if i.ServiceID == "" { + return nil, ErrMissingServiceID + } + if i.ServiceVersion == 0 { + return nil, ErrMissingServiceVersion + } + + path := fmt.Sprintf("/service/%s/version/%d/resource", i.ServiceID, i.ServiceVersion) + resp, err := c.PostForm(path, i, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var r *Resource + if err := decodeBodyMap(resp.Body, &r); err != nil { + return nil, err + } + return r, nil +} + +// GetResourceInput is used as input to the GetResource function. +type GetResourceInput struct { + // ResourceID is an alphanumeric string identifying the resource (required) + // + // NOTE: The API documentation is confusing here because they name the + // parameter `resource_id` but they actually mean (as far as their data model + // is concerned) the `id` field. `resource_id`, from the API perspective, is + // referring to the resource you're creating a link to (e.g. an object store). + ResourceID string + // ServiceID is the ID of the service (required). + ServiceID string + // ServiceVersion is the specific configuration version (required). + ServiceVersion int +} + +// GetResource retrieves the specified resource. +func (c *Client) GetResource(i *GetResourceInput) (*Resource, error) { + if i.ResourceID == "" { + return nil, ErrMissingResourceID + } + if i.ServiceID == "" { + return nil, ErrMissingServiceID + } + if i.ServiceVersion == 0 { + return nil, ErrMissingServiceVersion + } + + path := fmt.Sprintf("/service/%s/version/%d/resource/%s", i.ServiceID, i.ServiceVersion, url.PathEscape(i.ResourceID)) + resp, err := c.Get(path, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var r *Resource + if err := decodeBodyMap(resp.Body, &r); err != nil { + return nil, err + } + return r, nil +} + +// UpdateResourceInput is used as input to the UpdateResource function. +type UpdateResourceInput struct { + // Name is the name of the resource being linked to (e.g. an object store). + Name *string `url:"name,omitempty"` + // ResourceID is an alphanumeric string identifying the resource (required) + // + // NOTE: The API documentation is confusing here because they name the + // parameter `resource_id` but they actually mean (as far as their data model + // is concerned) the `id` field. `resource_id`, from the API perspective, is + // referring to the resource you're creating a link to (e.g. an object store). + ResourceID string `url:"-"` + // ServiceID is the ID of the service (required). + ServiceID string `url:"-"` + // ServiceVersion is the specific configuration version (required). + ServiceVersion int `url:"-"` +} + +// UpdateResource updates the specified resource. +func (c *Client) UpdateResource(i *UpdateResourceInput) (*Resource, error) { + if i.ResourceID == "" { + return nil, ErrMissingResourceID + } + if i.ServiceID == "" { + return nil, ErrMissingServiceID + } + if i.ServiceVersion == 0 { + return nil, ErrMissingServiceVersion + } + + path := fmt.Sprintf("/service/%s/version/%d/resource/%s", i.ServiceID, i.ServiceVersion, url.PathEscape(i.ResourceID)) + resp, err := c.PutForm(path, i, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var b *Resource + if err := decodeBodyMap(resp.Body, &b); err != nil { + return nil, err + } + return b, nil +} + +// DeleteResourceInput is the input parameter to DeleteResource. +type DeleteResourceInput struct { + // ResourceID is an alphanumeric string identifying the resource (required) + ResourceID string `url:"-"` + // ServiceID is the ID of the service (required). + ServiceID string `url:"-"` + // ServiceVersion is the specific configuration version (required). + ServiceVersion int `url:"-"` +} + +// DeleteResource deletes the specified resource. +func (c *Client) DeleteResource(i *DeleteResourceInput) error { + if i.ResourceID == "" { + return ErrMissingResourceID + } + if i.ServiceID == "" { + return ErrMissingServiceID + } + if i.ServiceVersion == 0 { + return ErrMissingServiceVersion + } + + path := fmt.Sprintf("/service/%s/version/%d/resource/%s", i.ServiceID, i.ServiceVersion, url.PathEscape(i.ResourceID)) + resp, err := c.Delete(path, nil) + if err != nil { + return err + } + defer resp.Body.Close() + + var r *statusResp + if err := decodeBodyMap(resp.Body, &r); err != nil { + return err + } + if !r.Ok() { + return fmt.Errorf("not ok") + } + return nil +} diff --git a/fastly/resource_test.go b/fastly/resource_test.go new file mode 100644 index 000000000..45f38bf8b --- /dev/null +++ b/fastly/resource_test.go @@ -0,0 +1,265 @@ +package fastly + +import ( + "testing" +) + +func TestClient_Resources(t *testing.T) { + t.Parallel() + + var err error + var tv *Version + record(t, "resources/version", func(c *Client) { + tv = testVersion(t, c) + }) + + // Create object-store resource we want to link to via Resource API. + var o *ObjectStore + record(t, "resources/create-object-store", func(c *Client) { + o, err = c.CreateObjectStore(&CreateObjectStoreInput{ + Name: "test-object-store", + }) + }) + if err != nil { + t.Fatal(err) + } + + // Ensure object-store resource is deleted + defer func() { + record(t, "resources/cleanup-object-store", func(c *Client) { + _ = c.DeleteObjectStore(&DeleteObjectStoreInput{ + ID: o.ID, + }) + }) + }() + + // NOTE: This doesn't have to match the actual object-store name. + // This is an opportunity for you to use an 'alias' for your object store. + // So your service will now refer to the object-store using this name. + const objectStoreNameForServiceLinking = "test-object-store-name-for-linking" + + // Create + var r *Resource + record(t, "resources/create", func(c *Client) { + r, err = c.CreateResource(&CreateResourceInput{ + ServiceID: testServiceID, + ServiceVersion: tv.Number, + Name: String(objectStoreNameForServiceLinking), + ResourceID: String(o.ID), + }) + }) + if err != nil { + t.Fatal(err) + } + + // Ensure deleted + defer func() { + record(t, "resources/cleanup", func(c *Client) { + // NOTE: The API documentation is confusing here because they name the + // parameter `resource_id` but they actually mean (as far as their data model + // is concerned) the `id` field. `resource_id`, from the API perspective, is + // referring to the resource you're creating a link to (e.g. an object store). + _ = c.DeleteResource(&DeleteResourceInput{ + ServiceID: testServiceID, + ServiceVersion: tv.Number, + ResourceID: r.ID, + }) + }) + }() + + if r.Name != objectStoreNameForServiceLinking { + t.Errorf("bad name: %q", r.Name) + } + + // List + var rs []*Resource + record(t, "resources/list", func(c *Client) { + rs, err = c.ListResources(&ListResourcesInput{ + ServiceID: testServiceID, + ServiceVersion: tv.Number, + }) + }) + if err != nil { + t.Fatal(err) + } + if len(rs) < 1 { + t.Errorf("bad resources: %v", rs) + } + + // Get + var gr *Resource + record(t, "resources/get", func(c *Client) { + // NOTE: The API documentation is confusing here because they name the + // parameter `resource_id` but they actually mean (as far as their data model + // is concerned) the `id` field. `resource_id`, from the API perspective, is + // referring to the resource you're creating a link to (e.g. an object store). + gr, err = c.GetResource(&GetResourceInput{ + ServiceID: testServiceID, + ServiceVersion: tv.Number, + ResourceID: r.ID, + }) + }) + if err != nil { + t.Fatal(err) + } + if r.Name != gr.Name { + t.Errorf("bad name: %q (%q)", r.Name, gr.Name) + } + + // Update + var ur *Resource + record(t, "resources/update", func(c *Client) { + // NOTE: The API documentation is confusing here because they name the + // parameter `resource_id` but they actually mean (as far as their data model + // is concerned) the `id` field. `resource_id`, from the API perspective, is + // referring to the resource you're creating a link to (e.g. an object store). + ur, err = c.UpdateResource(&UpdateResourceInput{ + ServiceID: testServiceID, + ServiceVersion: tv.Number, + ResourceID: r.ID, + Name: String("new-object-store-alias-for-my-service"), + }) + }) + if err != nil { + t.Fatal(err) + } + if ur.Name != "new-object-store-alias-for-my-service" { + t.Errorf("bad name: %q", ur.Name) + } + + // Delete + record(t, "resources/delete", func(c *Client) { + // NOTE: The API documentation is confusing here because they name the + // parameter `resource_id` but they actually mean (as far as their data model + // is concerned) the `id` field. `resource_id`, from the API perspective, is + // referring to the resource you're creating a link to (e.g. an object store). + err = c.DeleteResource(&DeleteResourceInput{ + ServiceID: testServiceID, + ServiceVersion: tv.Number, + ResourceID: ur.ID, + }) + }) + if err != nil { + t.Fatal(err) + } +} + +func TestClient_ListResources_validation(t *testing.T) { + var err error + _, err = testClient.ListResources(&ListResourcesInput{ + ServiceID: "", + }) + if err != ErrMissingServiceID { + t.Errorf("bad error: %s", err) + } + + _, err = testClient.ListResources(&ListResourcesInput{ + ServiceID: "foo", + ServiceVersion: 0, + }) + if err != ErrMissingServiceVersion { + t.Errorf("bad error: %s", err) + } +} + +func TestClient_CreateResource_validation(t *testing.T) { + var err error + _, err = testClient.CreateResource(&CreateResourceInput{ + ServiceID: "", + }) + if err != ErrMissingServiceID { + t.Errorf("bad error: %s", err) + } + + _, err = testClient.CreateResource(&CreateResourceInput{ + ServiceID: "foo", + ServiceVersion: 0, + }) + if err != ErrMissingServiceVersion { + t.Errorf("bad error: %s", err) + } +} + +func TestClient_GetResource_validation(t *testing.T) { + var err error + + _, err = testClient.GetResource(&GetResourceInput{ + ServiceID: "foo", + ServiceVersion: 1, + }) + if err != ErrMissingResourceID { + t.Errorf("bad error: %s", err) + } + + _, err = testClient.GetResource(&GetResourceInput{ + ResourceID: "test", + ServiceVersion: 1, + }) + if err != ErrMissingServiceID { + t.Errorf("bad error: %s", err) + } + + _, err = testClient.GetResource(&GetResourceInput{ + ResourceID: "test", + ServiceID: "foo", + }) + if err != ErrMissingServiceVersion { + t.Errorf("bad error: %s", err) + } +} + +func TestClient_UpdateResource_validation(t *testing.T) { + var err error + + _, err = testClient.UpdateResource(&UpdateResourceInput{ + ServiceID: "foo", + ServiceVersion: 1, + }) + if err != ErrMissingResourceID { + t.Errorf("bad error: %s", err) + } + + _, err = testClient.UpdateResource(&UpdateResourceInput{ + ResourceID: "test", + ServiceVersion: 1, + }) + if err != ErrMissingServiceID { + t.Errorf("bad error: %s", err) + } + + _, err = testClient.UpdateResource(&UpdateResourceInput{ + ResourceID: "test", + ServiceID: "foo", + }) + if err != ErrMissingServiceVersion { + t.Errorf("bad error: %s", err) + } +} + +func TestClient_DeleteResource_validation(t *testing.T) { + var err error + + err = testClient.DeleteResource(&DeleteResourceInput{ + ServiceID: "foo", + ServiceVersion: 1, + }) + if err != ErrMissingResourceID { + t.Errorf("bad error: %s", err) + } + + err = testClient.DeleteResource(&DeleteResourceInput{ + ResourceID: "test", + ServiceVersion: 1, + }) + if err != ErrMissingServiceID { + t.Errorf("bad error: %s", err) + } + + err = testClient.DeleteResource(&DeleteResourceInput{ + ResourceID: "test", + ServiceID: "foo", + }) + if err != ErrMissingServiceVersion { + t.Errorf("bad error: %s", err) + } +}