From 6de34a4b6dbadefa32150f91ba6c8af116b8d2eb Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Mon, 11 Aug 2025 14:13:33 +0100 Subject: [PATCH 1/7] feat: new upgrade path, refactoring tests --- database_test.go | 254 +++++++++++++++-------------------- service/databases/model.go | 8 ++ service/databases/service.go | 13 ++ util_test.go | 43 ++++++ 4 files changed, 173 insertions(+), 145 deletions(-) diff --git a/database_test.go b/database_test.go index e476e70..cb74eeb 100644 --- a/database_test.go +++ b/database_test.go @@ -3,6 +3,7 @@ package rediscloud_api import ( "context" "fmt" + "net/http" "net/http/httptest" "testing" "time" @@ -339,67 +340,51 @@ func TestDatabase_Get_wraps404Error(t *testing.T) { } func TestDatabase_Update(t *testing.T) { - s := httptest.NewServer(testServer("key", "secret", putRequest(t, "/subscriptions/42/databases/18", `{ - "dryRun": false, - "name": "example", - "datasetSizeInGb": 1, - "supportOSSClusterApi": false, - "respVersion": "resp3", - "useExternalEndpointForOSSClusterApi": false, - "dataEvictionPolicy": "allkeys-lru", - "replication": true, - "throughputMeasurement": { - "by": "operations-per-second", - "value": 1000 - }, - "regexRules": [".*"], - "dataPersistence": "none", - "replicaOf": [ - "another" - ], - "periodicBackupPath": "s3://bucket-name", - "sourceIp": [ - "10.0.0.1" - ], - "clientSslCertificate": "something", - "clientTlsCertificates": ["something", "new"], - "enableTls": false, - "password": "fooBar", - "alerts": [ - { - "name": "dataset-size", - "value": 80 - } - ], - "enableDefaultUser": false, - "queryPerformanceFactor": "2x" -}`, `{ - "taskId": "task", - "commandType": "databaseUpdateRequest", - "status": "received", - "description": "Task request received and is being queued for processing.", - "timestamp": "2020-11-02T09:05:34.3Z", - "_links": { - "task": { - "href": "https://example.org", - "title": "getTaskStatusUpdates", - "type": "GET" - } - } -}`), getRequest(t, "/tasks/task", `{ - "taskId": "task", - "commandType": "databaseUpdateRequest", - "status": "processing-completed", - "timestamp": "2020-10-28T09:58:16.798Z", - "response": { - }, - "_links": { - "self": { - "href": "https://example.com", - "type": "GET" - } - } -}`))) + flow := taskFlow( + t, + http.MethodPut, + "/subscriptions/42/databases/18", + `{ + "dryRun": false, + "name": "example", + "datasetSizeInGb": 1, + "supportOSSClusterApi": false, + "respVersion": "resp3", + "useExternalEndpointForOSSClusterApi": false, + "dataEvictionPolicy": "allkeys-lru", + "replication": true, + "throughputMeasurement": { + "by": "operations-per-second", + "value": 1000 + }, + "regexRules": [".*"], + "dataPersistence": "none", + "replicaOf": [ + "another" + ], + "periodicBackupPath": "s3://bucket-name", + "sourceIp": [ + "10.0.0.1" + ], + "clientSslCertificate": "something", + "clientTlsCertificates": ["something", "new"], + "enableTls": false, + "password": "fooBar", + "alerts": [ + { + "name": "dataset-size", + "value": 80 + } + ], + "enableDefaultUser": false, + "queryPerformanceFactor": "2x" + }`, + "task", + "databaseUpdateRequest", + ) + + s := httptest.NewServer(testServer("key", "secret", flow...)) + defer s.Close() subject, err := clientFromTestServer(s, "key", "secret") require.NoError(t, err) @@ -439,33 +424,17 @@ func TestDatabase_Update(t *testing.T) { } func TestDatabase_Delete(t *testing.T) { - s := httptest.NewServer(testServer("key", "secret", deleteRequest(t, "/subscriptions/42/databases/4291", `{ - "taskId": "task", - "commandType": "databaseDeleteRequest", - "status": "received", - "description": "Task request received and is being queued for processing.", - "timestamp": "2020-11-02T09:05:34.3Z", - "_links": { - "task": { - "href": "https://example.org", - "title": "getTaskStatusUpdates", - "type": "GET" - } - } -}`), getRequest(t, "/tasks/task", `{ - "taskId": "e02b40d6-1395-4861-a3b9-ecf829d835fd", - "commandType": "databaseDeleteRequest", - "status": "processing-completed", - "timestamp": "2020-10-28T09:58:16.798Z", - "response": { - }, - "_links": { - "self": { - "href": "https://example.com", - "type": "GET" - } - } -}`))) + flow := taskFlow( + t, + http.MethodDelete, + "/subscriptions/42/databases/4291", + "", + "task", + "databaseDeleteRequest", + ) + + s := httptest.NewServer(testServer("key", "secret", flow...)) + defer s.Close() subject, err := clientFromTestServer(s, "key", "secret") require.NoError(t, err) @@ -475,33 +444,17 @@ func TestDatabase_Delete(t *testing.T) { } func TestDatabase_Backup(t *testing.T) { - s := httptest.NewServer(testServer("key", "secret", postRequestWithNoRequest(t, "/subscriptions/42/databases/18/backup", `{ - "taskId": "task-uuid", - "commandType": "databaseBackupRequest", - "status": "received", - "description": "Task request received and is being queued for processing.", - "timestamp": "2020-11-02T09:05:34.3Z", - "_links": { - "task": { - "href": "https://example.org", - "title": "getTaskStatusUpdates", - "type": "GET" - } - } -}`), getRequest(t, "/tasks/task-uuid", `{ - "taskId": "task-uuid", - "commandType": "databaseBackupRequest", - "status": "processing-completed", - "timestamp": "2020-10-28T09:58:16.798Z", - "response": { - }, - "_links": { - "self": { - "href": "https://example.com", - "type": "GET" - } - } -}`))) + flow := taskFlow( + t, + http.MethodPost, + "/subscriptions/42/databases/18/backup", + "", + "task-uuid", + "databaseBackupRequest", + ) + + s := httptest.NewServer(testServer("key", "secret", flow...)) + defer s.Close() subject, err := clientFromTestServer(s, "key", "secret") require.NoError(t, err) @@ -511,36 +464,20 @@ func TestDatabase_Backup(t *testing.T) { } func TestDatabase_Import(t *testing.T) { - s := httptest.NewServer(testServer("key", "secret", postRequest(t, "/subscriptions/42/databases/81/import", `{ - "sourceType": "magic", - "importFromUri": ["tinkerbell"] -}`, `{ - "taskId": "task-uuid", - "commandType": "databaseImportRequest", - "status": "received", - "description": "Task request received and is being queued for processing.", - "timestamp": "2020-11-02T09:05:34.3Z", - "_links": { - "task": { - "href": "https://example.org", - "title": "getTaskStatusUpdates", - "type": "GET" - } - } -}`), getRequest(t, "/tasks/task-uuid", `{ - "taskId": "task-uuid", - "commandType": "databaseImportRequest", - "status": "processing-completed", - "timestamp": "2020-10-28T09:58:16.798Z", - "response": { - }, - "_links": { - "self": { - "href": "https://example.com", - "type": "GET" - } - } -}`))) + flow := taskFlow( + t, + http.MethodPost, + "/subscriptions/42/databases/81/import", + `{ + "sourceType": "magic", + "importFromUri": ["tinkerbell"] + }`, + "task-uuid", + "databaseImportRequest", + ) + + s := httptest.NewServer(testServer("key", "secret", flow...)) + defer s.Close() subject, err := clientFromTestServer(s, "key", "secret") require.NoError(t, err) @@ -567,3 +504,30 @@ func TestDatabase_Certificate(t *testing.T) { }, certificate) } + +func TestDatabase_UpgradeRedisVersion(t *testing.T) { + flow := taskFlow( + t, + http.MethodPost, + "/subscriptions/42/databases/18/upgrade", + `{ "targetRedisVersion": "7.2" }`, + "upgrade-task-id", + "databaseUpgradeRequest", + ) + + s := httptest.NewServer(testServer("key", "secret", flow...)) + defer s.Close() + + subject, err := clientFromTestServer(s, "key", "secret") + require.NoError(t, err) + + err = subject.Database.UpgradeRedisVersion( + context.TODO(), + 42, + 18, + databases.UpgradeRedisVersion{ + TargetRedisVersion: redis.String("7.2"), + }, + ) + require.NoError(t, err) +} diff --git a/service/databases/model.go b/service/databases/model.go index dabb72c..f835bff 100644 --- a/service/databases/model.go +++ b/service/databases/model.go @@ -198,6 +198,14 @@ type Import struct { ImportFromURI []*string `json:"importFromUri,omitempty"` } +type UpgradeRedisVersion struct { + TargetRedisVersion *string `json:"targetRedisVersion,omitempty"` +} + +func (o UpgradeRedisVersion) String() string { + return internal.ToString(o) +} + func (o Import) String() string { return internal.ToString(o) } diff --git a/service/databases/service.go b/service/databases/service.go index a05ee40..03a2548 100644 --- a/service/databases/service.go +++ b/service/databases/service.go @@ -86,6 +86,19 @@ func (a *API) Update(ctx context.Context, subscription int, database int, update return a.taskWaiter.Wait(ctx, *task.ID) } +// UpgradeRedisVersion will upgrade the Redis version of an existing database. +func (a *API) UpgradeRedisVersion(ctx context.Context, subscription int, database int, upgradeVersion UpgradeRedisVersion) error { + var task internal.TaskResponse + err := a.client.Post(ctx, fmt.Sprintf("upgrade database %d version for subscription %d", database, subscription), fmt.Sprintf("/subscriptions/%d/databases/%d/upgrade", subscription, database), upgradeVersion, &task) + if err != nil { + return err + } + + a.logger.Printf("Waiting for database %d for subscription %d to finish being upgraded", database, subscription) + + return a.taskWaiter.Wait(ctx, *task.ID) +} + // Delete will destroy an existing database. func (a *API) Delete(ctx context.Context, subscription int, database int) error { var task internal.TaskResponse diff --git a/util_test.go b/util_test.go index 7d0df5a..817a761 100644 --- a/util_test.go +++ b/util_test.go @@ -1,12 +1,14 @@ package rediscloud_api import ( + "fmt" "io/ioutil" "net/http" "net/http/httptest" "net/url" "strings" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -183,3 +185,44 @@ func putRequest(t *testing.T, path string, request string, body string) endpoint t: t, } } + +// taskFlow returns the two endpointRequests needed for a "POST/PUT/DELETE -> GET /tasks/{id}" flow +func taskFlow(t *testing.T, method, path, requestBody, taskID, commandType string) []endpointRequest { + now := time.Now().UTC().Format(time.RFC3339) // e.g. "2025-08-11T14:33:21Z" + + var first endpointRequest + responseTemplate := fmt.Sprintf(`{ + "taskId": "%s", + "commandType": "%s", + "status": "received", + "description": "Task queued.", + "timestamp": "%s", + "_links": { "task": { "href": "https://example.org", "title": "getTaskStatusUpdates", "type": "GET" } } + }`, taskID, commandType, now) + + switch method { + case http.MethodPost: + if requestBody != "" { + first = postRequest(t, path, requestBody, responseTemplate) + } else { + first = postRequestWithNoRequest(t, path, responseTemplate) + } + case http.MethodPut: + first = putRequest(t, path, requestBody, responseTemplate) + case http.MethodDelete: + first = deleteRequest(t, path, responseTemplate) + } + + completeTemplate := fmt.Sprintf(`{ + "taskId": "%s", + "commandType": "%s", + "status": "processing-completed", + "timestamp": "%s", + "response": {}, + "_links": { "self": { "href": "https://example.com", "type": "GET" } } + }`, taskID, commandType, now) + + second := getRequest(t, "/tasks/"+taskID, completeTemplate) + + return []endpointRequest{first, second} +} From dd81ce1c51d97663402f1d761ae2f81eaf98a469 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Mon, 11 Aug 2025 16:53:07 +0100 Subject: [PATCH 2/7] feat: get redis versions endpoint --- service/subscriptions/model.go | 11 +++++++++++ service/subscriptions/service.go | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/service/subscriptions/model.go b/service/subscriptions/model.go index 6804b0b..8e119a2 100644 --- a/service/subscriptions/model.go +++ b/service/subscriptions/model.go @@ -320,6 +320,17 @@ type ActiveActiveDatabase struct { WriteOperationsPerSecond *int `json:"writeOperationsPerSecond,omitempty"` } +type RedisVersion struct { + Version string `json:"version,omitempty"` + EolDate string `json:"eolDate,omitempty"` + IsPreview bool `json:"isPreview,omitempty"` + IsDefault bool `json:"isDefault,omitempty"` +} + +type RedisVersions struct { + RedisVersion []RedisVersion `json:"redisVersions,omitempty"` +} + type NotFound struct { ID int } diff --git a/service/subscriptions/service.go b/service/subscriptions/service.go index 12a04b9..36b194e 100644 --- a/service/subscriptions/service.go +++ b/service/subscriptions/service.go @@ -267,6 +267,24 @@ func (a *API) ListActiveActiveRegions(ctx context.Context, subscription int) ([] return response.Regions, nil } +// GetRedisVersions retrieves the Redis database versions available for this subscription. +func (a *API) GetRedisVersions(ctx context.Context, subscription int) (*RedisVersions, error) { + var redisVersions RedisVersions + getRedisVersionsUrl := "/subscriptions/redis-versions?subscriptionId=%d?" + + path := fmt.Sprintf(getRedisVersionsUrl, subscription) + err := a.client.Get( + ctx, + fmt.Sprintf("get versions for subscription %d", subscription), + path, + &redisVersions, + ) + if err != nil { + return nil, wrap404Error(subscription, err) + } + return &redisVersions, nil +} + func wrap404Error(id int, err error) error { if v, ok := err.(*internal.HTTPError); ok && v.StatusCode == http.StatusNotFound { return &NotFound{ID: id} From 122e0c25d03f4ba07c63d4f2e809015e3df575ed Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 12 Aug 2025 17:44:37 +0100 Subject: [PATCH 3/7] fix: fixed url --- service/subscriptions/service.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/service/subscriptions/service.go b/service/subscriptions/service.go index 36b194e..a5ce00d 100644 --- a/service/subscriptions/service.go +++ b/service/subscriptions/service.go @@ -270,7 +270,7 @@ func (a *API) ListActiveActiveRegions(ctx context.Context, subscription int) ([] // GetRedisVersions retrieves the Redis database versions available for this subscription. func (a *API) GetRedisVersions(ctx context.Context, subscription int) (*RedisVersions, error) { var redisVersions RedisVersions - getRedisVersionsUrl := "/subscriptions/redis-versions?subscriptionId=%d?" + getRedisVersionsUrl := "/subscriptions/redis-versions?subscriptionId=%d" path := fmt.Sprintf(getRedisVersionsUrl, subscription) err := a.client.Get( @@ -279,6 +279,7 @@ func (a *API) GetRedisVersions(ctx context.Context, subscription int) (*RedisVer path, &redisVersions, ) + if err != nil { return nil, wrap404Error(subscription, err) } From 0368553b2f5f1e803073b1da9522286b635f829c Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 12 Aug 2025 18:11:49 +0100 Subject: [PATCH 4/7] test: writing test for get redis versions --- service/subscriptions/model.go | 10 +++---- subscription_test.go | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/service/subscriptions/model.go b/service/subscriptions/model.go index 8e119a2..a6da602 100644 --- a/service/subscriptions/model.go +++ b/service/subscriptions/model.go @@ -321,14 +321,14 @@ type ActiveActiveDatabase struct { } type RedisVersion struct { - Version string `json:"version,omitempty"` - EolDate string `json:"eolDate,omitempty"` - IsPreview bool `json:"isPreview,omitempty"` - IsDefault bool `json:"isDefault,omitempty"` + Version *string `json:"version,omitempty"` + EolDate *string `json:"eolDate,omitempty"` + IsPreview *bool `json:"isPreview,omitempty"` + IsDefault *bool `json:"isDefault,omitempty"` } type RedisVersions struct { - RedisVersion []RedisVersion `json:"redisVersions,omitempty"` + RedisVersions []*RedisVersion `json:"redisVersions,omitempty"` } type NotFound struct { diff --git a/subscription_test.go b/subscription_test.go index 44927ed..3ab5513 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -1050,6 +1050,59 @@ func TestSubscription_ListActiveActiveRegions(t *testing.T) { } +func TestSubscription_GetRedisVersions(t *testing.T) { + + s := httptest.NewServer(testServer("apiKey", "secret", getRequest(t, "/subscriptions/redis-versions?subscriptionId=9291", ` +{ + "redisVersions": [ + { + "version": "6.2", + "isPreview": false, + "isDefault": false + }, + { + "version": "7.2", + "isPreview": false, + "isDefault": false + }, + { + "version": "7.4", + "isPreview": false, + "isDefault": true + } + ] +} +`))) + + subject, err := clientFromTestServer(s, "apiKey", "secret") + require.NoError(t, err) + + actual, err := subject.Subscription.GetRedisVersions(context.TODO(), 9291) + require.NoError(t, err) + + expected := &subscriptions.RedisVersions{ + RedisVersions: []*subscriptions.RedisVersion{ + { + Version: redis.String("6.2"), + IsPreview: redis.Bool(false), + IsDefault: redis.Bool(false), + }, + { + Version: redis.String("7.2"), + IsPreview: redis.Bool(false), + IsDefault: redis.Bool(false), + }, + { + Version: redis.String("7.4"), + IsPreview: redis.Bool(false), + IsDefault: redis.Bool(true), + }, + }, + } + + assert.Equal(t, expected, actual) +} + func listRegionsExpected() []*subscriptions.ActiveActiveRegion { // Initialize databases From af606398dc5dabcf10e13f952df7d0f860027fde Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 12 Aug 2025 18:14:51 +0100 Subject: [PATCH 5/7] docs: update changelog.md --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dfffa6..27ac707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/). +## 0.33.0 + +### Added: +* Upgrade Redis database endpoint +* Get Redis versions for subscription endpoint + +## 0.32.0 + +### Added + +* Adding redisVersion support on the create, get and list endpoints for pro databases. + ## 0.31.0 ### Added From 7387f0dec6350b0f6c8248170a920ad34089dec2 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Tue, 12 Aug 2025 18:15:34 +0100 Subject: [PATCH 6/7] docs: minor syntax update --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27ac707..b4e734a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,8 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/). ## 0.33.0 ### Added: -* Upgrade Redis database endpoint -* Get Redis versions for subscription endpoint +* Adding Upgrade Redis database endpoint +* Adding Get Redis versions for subscription endpoint ## 0.32.0 From 01740b6494071dae697668bda2337aa2a096b7d4 Mon Sep 17 00:00:00 2001 From: Matthew Long Date: Wed, 13 Aug 2025 09:47:29 +0100 Subject: [PATCH 7/7] chore: fmt --- subscription_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subscription_test.go b/subscription_test.go index 3ab5513..1b6cced 100644 --- a/subscription_test.go +++ b/subscription_test.go @@ -1099,7 +1099,7 @@ func TestSubscription_GetRedisVersions(t *testing.T) { }, }, } - + assert.Equal(t, expected, actual) }