From b81a4d69b6bd0554bb51534235cbb9f25d9256fd Mon Sep 17 00:00:00 2001 From: Wojciech Zagrajczuk Date: Thu, 4 Apr 2024 07:03:12 +0000 Subject: [PATCH] DXE-3536 Properties associated with access key --- CHANGELOG.md | 5 +- pkg/cloudaccess/cloudaccess.go | 16 ++ pkg/cloudaccess/errors.go | 9 +- pkg/cloudaccess/mocks.go | 30 +++ pkg/cloudaccess/properties.go | 200 +++++++++++++++ pkg/cloudaccess/properties_test.go | 396 +++++++++++++++++++++++++++++ 6 files changed, 651 insertions(+), 5 deletions(-) create mode 100644 pkg/cloudaccess/properties.go create mode 100644 pkg/cloudaccess/properties_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3231a342..31f2d2b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,10 @@ - + * Properties using Access Key + * [LookupProperties](https://techdocs.akamai.com/cloud-access-mgr/reference/get-access-key-version-properties) + * [GetAsyncPropertiesLookupID](https://techdocs.akamai.com/cloud-access-mgr/reference/get-async-version-property-lookup) + * [PerformAsyncPropertiesLookup](https://techdocs.akamai.com/cloud-access-mgr/reference/get-property-lookup) ## X.X.X (X X, X) diff --git a/pkg/cloudaccess/cloudaccess.go b/pkg/cloudaccess/cloudaccess.go index b2a32da9..d01b9812 100644 --- a/pkg/cloudaccess/cloudaccess.go +++ b/pkg/cloudaccess/cloudaccess.go @@ -70,6 +70,22 @@ type ( // // See: https://techdocs.akamai.com/cloud-access-mgr/reference/delete-access-key-version DeleteAccessKeyVersion(context.Context, DeleteAccessKeyVersionRequest) (*DeleteAccessKeyVersionResponse, error) + + // LookupProperties returns information about all the Property Manager properties that use a specific version of an access key. + // This operation gets the data directly. To avoid any latency problems, use the GetAsyncPropertiesLookupID and PerformAsyncPropertiesLookup + // + // See: https://techdocs.akamai.com/cloud-access-mgr/reference/get-access-key-version-properties + LookupProperties(ctx context.Context, params LookupPropertiesRequest) (*LookupPropertiesResponse, error) + + // GetAsyncPropertiesLookupID gets the unique identifier used to perform an PerformAsyncPropertiesLookup + // + // See: https://techdocs.akamai.com/cloud-access-mgr/reference/get-async-version-property-lookup + GetAsyncPropertiesLookupID(ctx context.Context, params GetAsyncPropertiesLookupIDRequest) (*GetAsyncPropertiesLookupIDResponse, error) + + // PerformAsyncPropertiesLookup returns in asynchronous way information about all the Property Manager properties that use a specific version of an access key. + // + // See: https://techdocs.akamai.com/cloud-access-mgr/reference/get-property-lookup + PerformAsyncPropertiesLookup(ctx context.Context, params PerformAsyncPropertiesLookupRequest) (*PerformAsyncPropertiesLookupResponse, error) } cloudaccess struct { diff --git a/pkg/cloudaccess/errors.go b/pkg/cloudaccess/errors.go index 4bad73c9..f90f14f8 100644 --- a/pkg/cloudaccess/errors.go +++ b/pkg/cloudaccess/errors.go @@ -18,10 +18,11 @@ type ( Title string `json:"title"` Detail string `json:"detail"` Instance string `json:"instance"` - Status int `json:"status"` - AccessKeyUID int `json:"accessKeyUid,omitempty"` + Status int64 `json:"status"` + AccessKeyUID int64 `json:"accessKeyUid,omitempty"` AccessKeyName string `json:"accessKeyName,omitempty"` ProblemID string `json:"problemId,omitempty"` + Version int64 `json:"version"` Errors []ErrorItem `json:"errors,omitempty"` } @@ -40,7 +41,7 @@ func (c *cloudaccess) Error(r *http.Response) error { body, err := io.ReadAll(r.Body) if err != nil { c.Log(r.Request.Context()).Errorf("reading error response body: %s", err) - e.Status = r.StatusCode + e.Status = int64(r.StatusCode) e.Title = fmt.Sprintf("Failed to read error body") e.Detail = err.Error() return &e @@ -52,7 +53,7 @@ func (c *cloudaccess) Error(r *http.Response) error { e.Detail = errs.UnescapeContent(string(body)) } - e.Status = r.StatusCode + e.Status = int64(r.StatusCode) return &e } diff --git a/pkg/cloudaccess/mocks.go b/pkg/cloudaccess/mocks.go index e7c3ee8d..292d484f 100644 --- a/pkg/cloudaccess/mocks.go +++ b/pkg/cloudaccess/mocks.go @@ -34,6 +34,36 @@ func (m *Mock) GetAccessKeyVersionStatus(ctx context.Context, r GetAccessKeyVers return args.Get(0).(*GetAccessKeyVersionStatusResponse), args.Error(1) } +func (m *Mock) LookupProperties(ctx context.Context, r LookupPropertiesRequest) (*LookupPropertiesResponse, error) { + args := m.Called(ctx, r) + + if args.Error(1) != nil { + return nil, args.Error(1) + } + + return args.Get(0).(*LookupPropertiesResponse), args.Error(1) +} + +func (m *Mock) GetAsyncPropertiesLookupID(ctx context.Context, r GetAsyncPropertiesLookupIDRequest) (*GetAsyncPropertiesLookupIDResponse, error) { + args := m.Called(ctx, r) + + if args.Error(1) != nil { + return nil, args.Error(1) + } + + return args.Get(0).(*GetAsyncPropertiesLookupIDResponse), args.Error(1) +} + +func (m *Mock) PerformAsyncPropertiesLookup(ctx context.Context, r PerformAsyncPropertiesLookupRequest) (*PerformAsyncPropertiesLookupResponse, error) { + args := m.Called(ctx, r) + + if args.Error(1) != nil { + return nil, args.Error(1) + } + + return args.Get(0).(*PerformAsyncPropertiesLookupResponse), args.Error(1) +} + func (m *Mock) CreateAccessKey(ctx context.Context, r CreateAccessKeyRequest) (*CreateAccessKeyResponse, error) { args := m.Called(ctx, r) diff --git a/pkg/cloudaccess/properties.go b/pkg/cloudaccess/properties.go new file mode 100644 index 00000000..20fe9558 --- /dev/null +++ b/pkg/cloudaccess/properties.go @@ -0,0 +1,200 @@ +package cloudaccess + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // LookupPropertiesRequest holds parameters for LookupProperties + LookupPropertiesRequest struct { + AccessKeyUID int64 + Version int64 + } + + // LookupPropertiesResponse contains response for LookupProperties + LookupPropertiesResponse struct { + Properties []Property `json:"properties"` + } + + // Property holds information about property related to given access key + Property struct { + AccessKeyUID int64 `json:"accessKeyUid"` + Version int64 `json:"version"` + PropertyID string `json:"propertyId"` + PropertyName string `json:"propertyName"` + ProductionVersion *int64 `json:"productionVersion"` + StagingVersion *int64 `json:"stagingVersion"` + } + + // GetAsyncPropertiesLookupIDRequest holds parameters for GetAsyncPropertiesLookupID + GetAsyncPropertiesLookupIDRequest struct { + AccessKeyUID int64 + Version int64 + } + + // GetAsyncPropertiesLookupIDResponse contains response for GetAsyncPropertiesLookupID + GetAsyncPropertiesLookupIDResponse struct { + LookupID int64 `json:"lookupId"` + RetryAfter int64 `json:"retryAfter"` + } + + // PerformAsyncPropertiesLookupRequest holds parameters for PerformAsyncPropertiesLookup + PerformAsyncPropertiesLookupRequest struct { + LookupID int64 + } + + // PerformAsyncPropertiesLookupResponse contains response for PerformAsyncPropertiesLookup + PerformAsyncPropertiesLookupResponse struct { + LookupID int64 `json:"lookupId"` + LookupStatus LookupStatus `json:"lookupStatus"` + Properties []Property `json:"properties"` + } + + // LookupStatus represents a lookup status + LookupStatus string +) + +const ( + // LookupComplete represents complete asynchronous property lookup status + LookupComplete LookupStatus = "COMPLETE" + // LookupError represents error asynchronous property lookup status + LookupError LookupStatus = "ERROR" + // LookupInProgress represents in progress asynchronous property lookup status + LookupInProgress LookupStatus = "IN_PROGRESS" + // LookupPending represents pending asynchronous property lookup status + LookupPending LookupStatus = "PENDING" + // LookupSubmitted represents submitted asynchronous property lookup status + LookupSubmitted LookupStatus = "SUBMITTED" +) + +// Validate validates LookupPropertiesRequest +func (r LookupPropertiesRequest) Validate() error { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Version": validation.Validate(r.Version, validation.Required), + "AccessKeyUID": validation.Validate(r.AccessKeyUID, validation.Required), + }) +} + +// Validate validates GetAsyncPropertiesLookupIDRequest +func (r GetAsyncPropertiesLookupIDRequest) Validate() interface{} { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "Version": validation.Validate(r.Version, validation.Required), + "AccessKeyUID": validation.Validate(r.AccessKeyUID, validation.Required), + }) +} + +// Validate validates PerformAsyncPropertiesLookupRequest +func (r PerformAsyncPropertiesLookupRequest) Validate() interface{} { + return edgegriderr.ParseValidationErrors(validation.Errors{ + "LookupID": validation.Validate(r.LookupID, validation.Required), + }) +} + +var ( + // ErrLookupProperties is returned when LookupProperties fails + ErrLookupProperties = errors.New("lookup properties") + // ErrGetAsyncLookupIDProperties is returned when GetAsyncPropertiesLookupID fails + ErrGetAsyncLookupIDProperties = errors.New("get lookup properties id async") + // ErrPerformAsyncLookupProperties is returned when PerformAsyncPropertiesLookup fails + ErrPerformAsyncLookupProperties = errors.New("perform async lookup properties") +) + +func (c *cloudaccess) LookupProperties(ctx context.Context, params LookupPropertiesRequest) (*LookupPropertiesResponse, error) { + logger := c.Log(ctx) + logger.Debug("LookupProperties") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrLookupProperties, ErrStructValidation, err) + } + + uri, err := url.Parse(fmt.Sprintf("/cam/v1/access-keys/%d/versions/%d/properties", params.AccessKeyUID, params.Version)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrLookupProperties, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrLookupProperties, err) + } + + var result LookupPropertiesResponse + resp, err := c.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrLookupProperties, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrLookupProperties, c.Error(resp)) + } + + return &result, nil +} + +func (c *cloudaccess) GetAsyncPropertiesLookupID(ctx context.Context, params GetAsyncPropertiesLookupIDRequest) (*GetAsyncPropertiesLookupIDResponse, error) { + logger := c.Log(ctx) + logger.Debug("GetAsyncPropertiesLookupID") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrGetAsyncLookupIDProperties, ErrStructValidation, err) + } + + uri, err := url.Parse(fmt.Sprintf("/cam/v1/access-keys/%d/versions/%d/property-lookup-id", params.AccessKeyUID, params.Version)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrGetAsyncLookupIDProperties, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetAsyncLookupIDProperties, err) + } + + var result GetAsyncPropertiesLookupIDResponse + resp, err := c.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrGetAsyncLookupIDProperties, err) + } + + if resp.StatusCode != http.StatusAccepted { + return nil, fmt.Errorf("%s: %w", ErrGetAsyncLookupIDProperties, c.Error(resp)) + } + + return &result, nil +} + +func (c *cloudaccess) PerformAsyncPropertiesLookup(ctx context.Context, params PerformAsyncPropertiesLookupRequest) (*PerformAsyncPropertiesLookupResponse, error) { + logger := c.Log(ctx) + logger.Debug("PerformAsyncPropertiesLookup") + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%s: %w: %s", ErrPerformAsyncLookupProperties, ErrStructValidation, err) + } + + uri, err := url.Parse(fmt.Sprintf("/cam/v1/property-lookups/%d", params.LookupID)) + if err != nil { + return nil, fmt.Errorf("%w: failed to parse url: %s", ErrPerformAsyncLookupProperties, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return nil, fmt.Errorf("%w: failed to create request: %s", ErrPerformAsyncLookupProperties, err) + } + + var result PerformAsyncPropertiesLookupResponse + resp, err := c.Exec(req, &result) + if err != nil { + return nil, fmt.Errorf("%w: request failed: %s", ErrPerformAsyncLookupProperties, err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %w", ErrPerformAsyncLookupProperties, c.Error(resp)) + } + + return &result, nil +} diff --git a/pkg/cloudaccess/properties_test.go b/pkg/cloudaccess/properties_test.go new file mode 100644 index 00000000..c5aa1135 --- /dev/null +++ b/pkg/cloudaccess/properties_test.go @@ -0,0 +1,396 @@ +package cloudaccess + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLookupProperties(t *testing.T) { + tests := map[string]struct { + request LookupPropertiesRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *LookupPropertiesResponse + withError func(*testing.T, error) + }{ + "200 OK empty": { + request: LookupPropertiesRequest{AccessKeyUID: 1234, Version: 1}, + responseStatus: http.StatusOK, + responseBody: ` +{ + "properties": [] +} +`, + expectedPath: "/cam/v1/access-keys/1234/versions/1/properties", + expectedResponse: &LookupPropertiesResponse{ + Properties: []Property{}, + }, + }, + "200 OK with properties": { + request: LookupPropertiesRequest{AccessKeyUID: 1234, Version: 1}, + responseStatus: http.StatusOK, + responseBody: ` +{ + "properties": [ + { + "accessKeyUid": 1234, + "version": 1, + "propertyId": "prp_5678", + "propertyName": "test-property", + "productionVersion": null, + "stagingVersion": 1 + }, + { + "accessKeyUid": 1234, + "version": 1, + "propertyId": "prp_6789", + "propertyName": "test-property2", + "productionVersion": 1, + "stagingVersion": null + } + ] +} +`, + expectedPath: "/cam/v1/access-keys/1234/versions/1/properties", + expectedResponse: &LookupPropertiesResponse{ + Properties: []Property{ + { + AccessKeyUID: 1234, + Version: 1, + PropertyID: "prp_5678", + PropertyName: "test-property", + ProductionVersion: nil, + StagingVersion: tools.Int64Ptr(1), + }, + { + AccessKeyUID: 1234, + Version: 1, + PropertyID: "prp_6789", + PropertyName: "test-property2", + ProductionVersion: tools.Int64Ptr(1), + StagingVersion: nil, + }, + }, + }, + }, + "404 incorrect request": { + request: LookupPropertiesRequest{AccessKeyUID: 1234, Version: 10}, + responseStatus: http.StatusNotFound, + responseBody: ` +{ + "type": "/cam/error-types/access-key-version-does-not-exist", + "title": "Domain Error", + "instance": "60126c0d-67f5-473c-bea0-16daa836dc44", + "status": 404, + "detail": "Version '10' for access key '1234' does not exist.", + "problemId": "60126c0d-67f5-473c-bea0-16daa836dc44", + "version": 10, + "accessKeyUID": 1234 +} +`, + expectedPath: "/cam/v1/access-keys/1234/versions/10/properties", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "/cam/error-types/access-key-version-does-not-exist", + Title: "Domain Error", + Detail: "Version '10' for access key '1234' does not exist.", + Instance: "60126c0d-67f5-473c-bea0-16daa836dc44", + Status: 404, + ProblemID: "60126c0d-67f5-473c-bea0-16daa836dc44", + AccessKeyUID: 1234, + Version: 10, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + request: LookupPropertiesRequest{AccessKeyUID: 1234, Version: 1}, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal-server-error", + "title": "Internal Server Error", + "detail": "Error processing request", + "instance": "TestInstances", + "status": 500 +} +`, + expectedPath: "/cam/v1/access-keys/1234/versions/1/properties", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal-server-error", + Title: "Internal Server Error", + Detail: "Error processing request", + Instance: "TestInstances", + Status: 500, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "validate errors": { + request: LookupPropertiesRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "lookup properties: struct validation: AccessKeyUID: cannot be blank\nVersion: cannot be blank", err.Error()) + }, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(test.responseStatus) + _, err := w.Write([]byte(test.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.LookupProperties(context.Background(), test.request) + if test.withError != nil { + test.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expectedResponse, result) + }) + } +} + +func TestGetAsyncPropertiesLookupID(t *testing.T) { + tests := map[string]struct { + request GetAsyncPropertiesLookupIDRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *GetAsyncPropertiesLookupIDResponse + withError func(*testing.T, error) + }{ + "200 OK": { + request: GetAsyncPropertiesLookupIDRequest{AccessKeyUID: 1234, Version: 1}, + responseStatus: http.StatusAccepted, + responseBody: ` +{ + "lookupId": 4321, + "retryAfter": 10 +} +`, + expectedPath: "/cam/v1/access-keys/1234/versions/1/property-lookup-id", + expectedResponse: &GetAsyncPropertiesLookupIDResponse{LookupID: 4321, RetryAfter: 10}, + }, + "404 incorrect request": { + request: GetAsyncPropertiesLookupIDRequest{AccessKeyUID: 1234, Version: 10}, + responseStatus: http.StatusNotFound, + responseBody: ` +{ + "type": "/cam/error-types/access-key-version-does-not-exist", + "title": "Domain Error", + "instance": "60126c0d-67f5-473c-bea0-16daa836dc44", + "status": 404, + "detail": "Version '10' for access key '1234' does not exist.", + "problemId": "60126c0d-67f5-473c-bea0-16daa836dc44", + "version": 10, + "accessKeyUID": 1234 +} +`, + expectedPath: "/cam/v1/access-keys/1234/versions/10/property-lookup-id", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "/cam/error-types/access-key-version-does-not-exist", + Title: "Domain Error", + Detail: "Version '10' for access key '1234' does not exist.", + Instance: "60126c0d-67f5-473c-bea0-16daa836dc44", + Status: 404, + ProblemID: "60126c0d-67f5-473c-bea0-16daa836dc44", + AccessKeyUID: 1234, + Version: 10, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "500 internal server error": { + request: GetAsyncPropertiesLookupIDRequest{AccessKeyUID: 1234, Version: 1}, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal-server-error", + "title": "Internal Server Error", + "detail": "Error processing request", + "instance": "TestInstances", + "status": 500 +} +`, + expectedPath: "/cam/v1/access-keys/1234/versions/1/property-lookup-id", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal-server-error", + Title: "Internal Server Error", + Detail: "Error processing request", + Instance: "TestInstances", + Status: 500, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "validate errors": { + request: GetAsyncPropertiesLookupIDRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "get lookup properties id async: struct validation: AccessKeyUID: cannot be blank\nVersion: cannot be blank", err.Error()) + }, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(test.responseStatus) + _, err := w.Write([]byte(test.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.GetAsyncPropertiesLookupID(context.Background(), test.request) + if test.withError != nil { + test.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expectedResponse, result) + }) + } +} + +func TestPerformAsyncPropertiesLookup(t *testing.T) { + tests := map[string]struct { + request PerformAsyncPropertiesLookupRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *PerformAsyncPropertiesLookupResponse + withError func(*testing.T, error) + }{ + "200 OK empty": { + request: PerformAsyncPropertiesLookupRequest{LookupID: 4321}, + responseStatus: http.StatusOK, + responseBody: ` +{ + "lookupId": 4321, + "lookupStatus": "COMPLETE", + "properties": [] +} +`, + expectedPath: "/cam/v1/property-lookups/4321", + expectedResponse: &PerformAsyncPropertiesLookupResponse{ + LookupID: 4321, + LookupStatus: "COMPLETE", + Properties: []Property{}, + }, + }, + "200 OK with properties": { + request: PerformAsyncPropertiesLookupRequest{LookupID: 4321}, + responseStatus: http.StatusOK, + responseBody: ` +{ + "lookupId": 4321, + "lookupStatus": "COMPLETE", + "properties": [ + { + "accessKeyUid": 1234, + "version": 1, + "propertyId": "prp_5678", + "propertyName": "test-property", + "productionVersion": null, + "stagingVersion": 1 + }, + { + "accessKeyUid": 1234, + "version": 1, + "propertyId": "prp_6789", + "propertyName": "test-property2", + "productionVersion": 1, + "stagingVersion": null + } + ] +} +`, + expectedPath: "/cam/v1/property-lookups/4321", + expectedResponse: &PerformAsyncPropertiesLookupResponse{ + LookupID: 4321, + LookupStatus: LookupComplete, + Properties: []Property{ + { + AccessKeyUID: 1234, + Version: 1, + PropertyID: "prp_5678", + PropertyName: "test-property", + ProductionVersion: nil, + StagingVersion: tools.Int64Ptr(1), + }, + { + AccessKeyUID: 1234, + Version: 1, + PropertyID: "prp_6789", + PropertyName: "test-property2", + ProductionVersion: tools.Int64Ptr(1), + StagingVersion: nil, + }, + }, + }, + }, + "500 internal server error": { + request: PerformAsyncPropertiesLookupRequest{LookupID: 4321}, + responseStatus: http.StatusInternalServerError, + responseBody: ` + { + "type": "internal-server-error", + "title": "Internal Server Error", + "detail": "Error processing request", + "instance": "TestInstances", + "status": 500 + } + `, + expectedPath: "/cam/v1/property-lookups/4321", + withError: func(t *testing.T, err error) { + want := &Error{ + Type: "internal-server-error", + Title: "Internal Server Error", + Detail: "Error processing request", + Instance: "TestInstances", + Status: 500, + } + assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) + }, + }, + "validate errors": { + request: PerformAsyncPropertiesLookupRequest{}, + withError: func(t *testing.T, err error) { + assert.Equal(t, "perform async lookup properties: struct validation: LookupID: cannot be blank", err.Error()) + }, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(test.responseStatus) + _, err := w.Write([]byte(test.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.PerformAsyncPropertiesLookup(context.Background(), test.request) + if test.withError != nil { + test.withError(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expectedResponse, result) + }) + } +}