diff --git a/client/client.go b/client/client.go index 11796b6..52101a8 100644 --- a/client/client.go +++ b/client/client.go @@ -34,6 +34,16 @@ func NewWithBaseURL(apiKey, baseURL string) *Client { } } +// NewWithHTTPClient creates a new client with a custom HTTP client. +// This is useful for testing with tools like go-vcr. +func NewWithHTTPClient(apiKey, baseURL string, httpClient *http.Client) *Client { + return &Client{ + apiKey: apiKey, + baseURL: baseURL, + httpClient: httpClient, + } +} + func (c *Client) makeRequest(method, endpoint string, body any) ([]byte, error) { var reqBody io.Reader var contentType string diff --git a/client/common_operations_vcr_test.go b/client/common_operations_vcr_test.go new file mode 100644 index 0000000..e02e2ae --- /dev/null +++ b/client/common_operations_vcr_test.go @@ -0,0 +1,156 @@ +package client + +import ( + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v2/cassette" + "gopkg.in/dnaeon/go-vcr.v2/recorder" +) + +// TestListInstancesVCR tests listing all instances +func TestListInstancesVCR(t *testing.T) { + r, err := recorder.New("fixtures/list_instances") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + // Add sanitization filters + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + if apiKey == "" { + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + instances, err := client.ListInstances() + + require.NoError(t, err) + require.NotNil(t, instances) + // Should have at least some instances (or empty list is OK) + t.Logf("✓ Listed %d instances", len(instances)) +} + +// TestGetInstanceVCR tests getting a specific instance +func TestGetInstanceVCR(t *testing.T) { + r, err := recorder.New("fixtures/get_instance") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + if apiKey == "" { + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + // Use an existing instance ID (should match cassette) + instanceID := 359563 + + instance, err := client.GetInstance(instanceID) + + require.NoError(t, err) + require.NotNil(t, instance) + assert.Equal(t, instanceID, instance.ID) + t.Logf("✓ Got instance %d: %s (plan: %s)", instance.ID, instance.Name, instance.Plan) +} + +// TestListRegionsVCR tests listing all regions +func TestListRegionsVCR(t *testing.T) { + r, err := recorder.New("fixtures/list_regions") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + if apiKey == "" { + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + regions, err := client.ListRegions("") + + require.NoError(t, err) + require.NotEmpty(t, regions) + t.Logf("✓ Listed %d regions", len(regions)) + + // Verify some expected fields + if len(regions) > 0 { + assert.NotEmpty(t, regions[0].Name) + assert.NotEmpty(t, regions[0].Provider) + t.Logf(" Example: %s (%s)", regions[0].Name, regions[0].Provider) + } +} + +// TestListPlansVCR tests listing all plans +func TestListPlansVCR(t *testing.T) { + r, err := recorder.New("fixtures/list_plans") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + if apiKey == "" { + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + plans, err := client.ListPlans("") + + require.NoError(t, err) + require.NotEmpty(t, plans) + t.Logf("✓ Listed %d plans", len(plans)) + + // Verify some expected fields and find bunny-1 + bunnyFound := false + for _, plan := range plans { + assert.NotEmpty(t, plan.Name) + if plan.Name == "bunny-1" { + bunnyFound = true + t.Logf(" Found bunny-1 plan") + } + } + assert.True(t, bunnyFound, "Should find bunny-1 plan") +} diff --git a/client/fixtures/bunny1_create.yaml b/client/fixtures/bunny1_create.yaml new file mode 100644 index 0000000..e108077 --- /dev/null +++ b/client/fixtures/bunny1_create.yaml @@ -0,0 +1,54 @@ +--- +version: 1 +interactions: + - request: + body: name=bunny1-test&plan=bunny-1®ion=amazon-web-services%3A%3Aus-east-1&tags%5B%5D=test&tags%5B%5D=bunny1 + form: + name: + - bunny1-test + plan: + - bunny-1 + region: + - amazon-web-services::us-east-1 + tags[]: + - test + - bunny1 + headers: + Content-Type: + - application/x-www-form-urlencoded + url: https://customer.cloudamqp.com/api/instances + method: POST + response: + body: '{"apikey":"REDACTED","id":359564,"message":"Your dedicated instance will be available within a couple of minutes","url":"amqps://REDACTED:REDACTED@test-fast-plum-quail.rmq7.cloudamqp.com/udhldnlq"}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "249" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:13:15 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=CtumpQyVXDpMYq2G1aI%2BN87AkNEWkQQRtNtC%2BZ%2BPmhA%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101595"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=CtumpQyVXDpMYq2G1aI%2BN87AkNEWkQQRtNtC%2BZ%2BPmhA%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101595" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 5118a2cd-9728-5577-61d6-6659e4cc769c + status: 200 OK + code: 200 + duration: 972.057374ms diff --git a/client/fixtures/bunny1_delete.yaml b/client/fixtures/bunny1_delete.yaml new file mode 100644 index 0000000..a256b5e --- /dev/null +++ b/client/fixtures/bunny1_delete.yaml @@ -0,0 +1,39 @@ +--- +version: 1 +interactions: + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/instances/359559 + method: DELETE + response: + body: "" + headers: + Cache-Control: + - no-cache + Date: + - Tue, 25 Nov 2025 20:21:27 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=6CMwcmhwCQkJWao6kfw%2FQ%2B1uNyspXxu6IlEsbprvy8E%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102087"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=6CMwcmhwCQkJWao6kfw%2FQ%2B1uNyspXxu6IlEsbprvy8E%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102087" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 64b2640d-5c01-4d69-7e44-74f92825df43 + status: 204 No Content + code: 204 + duration: 710.235286ms diff --git a/client/fixtures/bunny1_to_hare1_update.yaml b/client/fixtures/bunny1_to_hare1_update.yaml new file mode 100644 index 0000000..c7e761e --- /dev/null +++ b/client/fixtures/bunny1_to_hare1_update.yaml @@ -0,0 +1,213 @@ +--- +version: 1 +interactions: + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/instances/359564 + method: GET + response: + body: '{"apikey":"REDACTED","backend":"rabbitmq","hostname_external":"test-fast-plum-quail.rmq7.cloudamqp.com","hostname_internal":"test-fast-plum-quail.in.rmq7.cloudamqp.com","id":359564,"name":"bunny1-test","nodes":1,"plan":"bunny-1","providerid":"00bfc123-4a0f-4ad7-aa11-a76587e2b9cf","ready":false,"region":"amazon-web-services::us-east-1","rmq_version":null,"tags":["test","bunny1"],"url":"amqps://REDACTED:REDACTED@test-fast-plum-quail.rmq7.cloudamqp.com/udhldnlq","urls":{"external":"amqps://REDACTED:REDACTED@test-fast-plum-quail.rmq7.cloudamqp.com/udhldnlq","internal":"amqp://REDACTED:REDACTED@test-fast-plum-quail.in.rmq7.cloudamqp.com/udhldnlq"}}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "751" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:15:30 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=fF68AhN2wX12EpXg%2B2yqwkxsRHL6Knvwe4Bgp735bgE%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101730"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=fF68AhN2wX12EpXg%2B2yqwkxsRHL6Knvwe4Bgp735bgE%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101730" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - c15a81c8-e2b9-b05d-5ea2-1f369caae5f6 + status: 200 OK + code: 200 + duration: 515.8422ms + - request: + body: plan=hare-1 + form: + plan: + - hare-1 + headers: + Content-Type: + - application/x-www-form-urlencoded + url: https://customer.cloudamqp.com/api/instances/359564 + method: PUT + response: + body: '{"error":"Please wait for your cluster to be configured before trying to update it"}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "84" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:15:31 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=JtK8mt10FyROYGew1pP%2Fjg8%2FI0TJZIAezwD3%2FkDRIXg%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101731"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=JtK8mt10FyROYGew1pP%2Fjg8%2FI0TJZIAezwD3%2FkDRIXg%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101731" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 879309d0-6944-88f3-cf4f-a2452daf8a12 + status: 422 Unprocessable Entity + code: 422 + duration: 255.167644ms + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/instances/359559 + method: GET + response: + body: '{"apikey":"REDACTED","backend":"rabbitmq","hostname_external":"test-smart-black-horse.rmq7.cloudamqp.com","hostname_internal":"test-smart-black-horse.in.rmq7.cloudamqp.com","id":359559,"name":"bunny1-test","nodes":1,"plan":"bunny-1","providerid":"4e683759-5eb9-41b3-8794-26f751de5699","ready":true,"region":"amazon-web-services::us-east-1","rmq_version":"4.1.4","tags":["test","bunny1"],"url":"amqps://REDACTED:REDACTED@test-smart-black-horse.rmq7.cloudamqp.com/nnmueodb","urls":{"external":"amqps://REDACTED:REDACTED@test-smart-black-horse.rmq7.cloudamqp.com/nnmueodb","internal":"amqp://REDACTED:REDACTED@test-smart-black-horse.in.rmq7.cloudamqp.com/nnmueodb"}}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "763" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:21:15 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=hvrv7ZgyfwrFm9XCVpdGEKCMIFyy8XtWfN5%2FDeMs%2FEk%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102075"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=hvrv7ZgyfwrFm9XCVpdGEKCMIFyy8XtWfN5%2FDeMs%2FEk%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102075" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - f5af072f-1111-89f8-aaf7-2b01eae6d702 + status: 200 OK + code: 200 + duration: 623.661279ms + - request: + body: plan=hare-1 + form: + plan: + - hare-1 + headers: + Content-Type: + - application/x-www-form-urlencoded + url: https://customer.cloudamqp.com/api/instances/359559 + method: PUT + response: + body: '{"message":"Your cluster is being reconfigured, this can take up to 10 minutes to complete. "}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "94" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:21:18 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=VmgOWbKN5g8l3Ecpc7bJj%2BI6wkXiR4PpR7jD9qiQltU%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102076"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=VmgOWbKN5g8l3Ecpc7bJj%2BI6wkXiR4PpR7jD9qiQltU%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102076" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 01357792-31cc-e945-2889-16758d2d0ccf + status: 200 OK + code: 200 + duration: 2.660481501s + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/instances/359559 + method: GET + response: + body: '{"error":"Not authorized"}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "26" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:22:00 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=h4Akh%2FVqf961p58OD38b6Ofdoy5No0txviQulRwc2%2Bo%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102120"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=h4Akh%2FVqf961p58OD38b6Ofdoy5No0txviQulRwc2%2Bo%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102120" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + Www-Authenticate: + - Basic realm="Customer API" + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 60d51287-c852-d98b-34a1-0c7c25573818 + status: 401 Unauthorized + code: 401 + duration: 439.921068ms diff --git a/client/fixtures/create_instance.yaml b/client/fixtures/create_instance.yaml new file mode 100644 index 0000000..0c951a5 --- /dev/null +++ b/client/fixtures/create_instance.yaml @@ -0,0 +1,54 @@ +--- +version: 1 +interactions: + - request: + body: name=vcr-test-instance&plan=lemur®ion=amazon-web-services%3A%3Aus-east-1&tags%5B%5D=test&tags%5B%5D=vcr + form: + name: + - vcr-test-instance + plan: + - lemur + region: + - amazon-web-services::us-east-1 + tags[]: + - test + - vcr + headers: + Content-Type: + - application/x-www-form-urlencoded + url: https://customer.cloudamqp.com/api/instances + method: POST + response: + body: '{"apikey":"REDACTED","id":359563,"message":"Instance created","url":"amqps://REDACTED:REDACTED@chimpanzee.rmq.cloudamqp.com/fbojnesu"}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "186" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:13:13 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=LZU5madntxgPF9xmSssx1sMPrGQxB7oaUN0jzqYvea8%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101593"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=LZU5madntxgPF9xmSssx1sMPrGQxB7oaUN0jzqYvea8%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101593" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 64cf6bec-e964-bafe-c9a6-9306fa74f657 + status: 200 OK + code: 200 + duration: 818.233145ms diff --git a/client/fixtures/get_instance.yaml b/client/fixtures/get_instance.yaml new file mode 100644 index 0000000..a9dbb72 --- /dev/null +++ b/client/fixtures/get_instance.yaml @@ -0,0 +1,43 @@ +--- +version: 1 +interactions: + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/instances/359563 + method: GET + response: + body: '{"apikey":"REDACTED","backend":"rabbitmq","hostname_external":"chimpanzee.rmq.cloudamqp.com","hostname_internal":"chimpanzee.in.cloudamqp.com","id":359563,"name":"vcr-test-instance","plan":"lemur","providerid":"aa3fb205-00c3-432c-b70b-e000f5bd32b2","ready":true,"region":"amazon-web-services::us-east-1","rmq_version":"4.0.5","tags":["test","vcr"],"url":"amqps://REDACTED:REDACTED@chimpanzee.rmq.cloudamqp.com/fbojnesu","urls":{"external":"amqps://REDACTED:REDACTED@chimpanzee.rmq.cloudamqp.com/fbojnesu","internal":"amqp://REDACTED:REDACTED@chimpanzee.in.cloudamqp.com/fbojnesu"}}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "681" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:15:08 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=uV02BRcjHyBpy6yxGWq5kwKOafKTXKXr6Vahky5u618%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101708"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=uV02BRcjHyBpy6yxGWq5kwKOafKTXKXr6Vahky5u618%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101708" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 2055a361-d3f7-9564-4938-7dd09562d774 + status: 200 OK + code: 200 + duration: 599.706475ms diff --git a/client/fixtures/list_instances.yaml b/client/fixtures/list_instances.yaml new file mode 100644 index 0000000..d7a1426 --- /dev/null +++ b/client/fixtures/list_instances.yaml @@ -0,0 +1,43 @@ +--- +version: 1 +interactions: + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/instances + method: GET + response: + body: '[{"id":359560,"name":"vcr-test-instance","plan":"lemur","region":"amazon-web-services::us-east-1","tags":["test","vcr"],"providerid":"e0c6e389-64bf-467d-b018-13c1a1d2636f","vpc_id":null},{"id":359559,"name":"bunny1-test","plan":"bunny-1","region":"amazon-web-services::us-east-1","tags":["test","bunny1"],"providerid":"4e683759-5eb9-41b3-8794-26f751de5699","vpc_id":null}]' + headers: + Cache-Control: + - no-cache + Content-Length: + - "372" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:13:02 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=UYKsnuWxUGZqcEJ65T3Hn8hL53RTgonxLqH3ufWgKIE%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101582"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=UYKsnuWxUGZqcEJ65T3Hn8hL53RTgonxLqH3ufWgKIE%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101582" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - d35d1258-3434-0587-ad7b-e8671b8811af + status: 200 OK + code: 200 + duration: 484.331845ms diff --git a/client/fixtures/list_plans.yaml b/client/fixtures/list_plans.yaml new file mode 100644 index 0000000..095a7f1 --- /dev/null +++ b/client/fixtures/list_plans.yaml @@ -0,0 +1,43 @@ +--- +version: 1 +interactions: + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/plans + method: GET + response: + body: '[{"name":"lemming","price":0,"backend":"lavinmq","shared":true},{"name":"vpn","price":0,"backend":"vpn","shared":false},{"name":"lemur","price":0,"backend":"rabbitmq","shared":true},{"name":"tiger","price":19,"backend":"rabbitmq","shared":true},{"name":"ermine","price":19,"backend":"lavinmq","shared":true},{"name":"puffin-1","price":49,"backend":"lavinmq","shared":false},{"name":"squirrel-1","price":50,"backend":"rabbitmq","shared":false},{"name":"bunny-1","price":99,"backend":"rabbitmq","shared":false},{"name":"penguin-1","price":99,"backend":"lavinmq","shared":false},{"name":"vpc","price":99,"backend":"vpc","shared":false},{"name":"puffin-3","price":147,"backend":"lavinmq","shared":false},{"name":"fox-1","price":149,"backend":"lavinmq","shared":false},{"name":"lynx-1","price":199,"backend":"lavinmq","shared":false},{"name":"hare-1","price":199,"backend":"rabbitmq","shared":false},{"name":"puffin-5","price":245,"backend":"lavinmq","shared":false},{"name":"bunny-3","price":297,"backend":"rabbitmq","shared":false},{"name":"penguin-3","price":297,"backend":"lavinmq","shared":false},{"name":"rabbit-1","price":299,"backend":"rabbitmq","shared":false},{"name":"penguin-5","price":495,"backend":"lavinmq","shared":false},{"name":"panda-1","price":499,"backend":"rabbitmq","shared":false},{"name":"leopard-1","price":499,"backend":"lavinmq","shared":false},{"name":"wolverine-1","price":499,"backend":"lavinmq","shared":false},{"name":"lynx-3","price":597,"backend":"lavinmq","shared":false},{"name":"hare-3","price":597,"backend":"rabbitmq","shared":false},{"name":"rabbit-3","price":897,"backend":"rabbitmq","shared":false},{"name":"lynx-5","price":995,"backend":"lavinmq","shared":false},{"name":"reindeer-1","price":999,"backend":"lavinmq","shared":false},{"name":"ape-1","price":999,"backend":"rabbitmq","shared":false},{"name":"rabbit-5","price":1495,"backend":"rabbitmq","shared":false},{"name":"panda-3","price":1497,"backend":"rabbitmq","shared":false},{"name":"wolverine-3","price":1497,"backend":"lavinmq","shared":false},{"name":"hippo-1","price":1999,"backend":"rabbitmq","shared":false},{"name":"bear-1","price":1999,"backend":"lavinmq","shared":false},{"name":"wolverine-5","price":2495,"backend":"lavinmq","shared":false},{"name":"panda-5","price":2495,"backend":"rabbitmq","shared":false},{"name":"ape-3","price":2997,"backend":"rabbitmq","shared":false},{"name":"reindeer-3","price":2997,"backend":"lavinmq","shared":false},{"name":"orca-1","price":2999,"backend":"lavinmq","shared":false},{"name":"lion-1","price":3499,"backend":"rabbitmq","shared":false},{"name":"ape-5","price":4995,"backend":"rabbitmq","shared":false},{"name":"reindeer-5","price":4995,"backend":"lavinmq","shared":false},{"name":"rhino-1","price":5499,"backend":"rabbitmq","shared":false},{"name":"hippo-3","price":5997,"backend":"rabbitmq","shared":false},{"name":"bear-3","price":5997,"backend":"lavinmq","shared":false},{"name":"orca-3","price":8997,"backend":"lavinmq","shared":false},{"name":"hippo-5","price":9995,"backend":"rabbitmq","shared":false},{"name":"bear-5","price":9995,"backend":"lavinmq","shared":false},{"name":"lion-3","price":10497,"backend":"rabbitmq","shared":false},{"name":"orca-5","price":14995,"backend":"lavinmq","shared":false},{"name":"rhino-3","price":16497,"backend":"rabbitmq","shared":false},{"name":"lion-5","price":17495,"backend":"rabbitmq","shared":false},{"name":"rhino-5","price":27495,"backend":"rabbitmq","shared":false}]' + headers: + Cache-Control: + - no-cache + Content-Length: + - "3465" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:13:03 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=dQAhEor6kU%2BZtt%2FoTKfG2D4ZerE3GLMhy78nLerGTHs%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101583"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=dQAhEor6kU%2BZtt%2FoTKfG2D4ZerE3GLMhy78nLerGTHs%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101583" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - a21049d7-2934-c8b1-59f2-14c07288d090 + status: 200 OK + code: 200 + duration: 129.801251ms diff --git a/client/fixtures/list_regions.yaml b/client/fixtures/list_regions.yaml new file mode 100644 index 0000000..f64291a --- /dev/null +++ b/client/fixtures/list_regions.yaml @@ -0,0 +1,43 @@ +--- +version: 1 +interactions: + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/regions + method: GET + response: + body: '[{"provider":"amazon-web-services","region":"sa-east-1","name":"Amazon Web Services - SA-East-1 (São Paulo)","has_shared_plans":true},{"provider":"amazon-web-services","region":"us-east-1","name":"Amazon Web Services - US-East-1 (Northern Virginia)","has_shared_plans":true},{"provider":"amazon-web-services","region":"us-east-2","name":"Amazon Web Services - US-East-2 (Ohio)","has_shared_plans":true},{"provider":"amazon-web-services","region":"eu-central-1","name":"Amazon Web Services - EU-Central-1 (Frankfurt)","has_shared_plans":true},{"provider":"amazon-web-services","region":"eu-north-1","name":"Amazon Web Services - EU-North-1 (Stockholm)","has_shared_plans":true},{"provider":"amazon-web-services","region":"us-west-2","name":"Amazon Web Services - US-West-2 (Oregon)","has_shared_plans":true},{"provider":"amazon-web-services","region":"us-west-1","name":"Amazon Web Services - US-West-1 (Northern California)","has_shared_plans":true},{"provider":"amazon-web-services","region":"eu-west-1","name":"Amazon Web Services - EU-West-1 (Ireland)","has_shared_plans":true},{"provider":"amazon-web-services","region":"eu-west-2","name":"Amazon Web Services - EU-West-2 (London)","has_shared_plans":true},{"provider":"amazon-web-services","region":"eu-west-3","name":"Amazon Web Services - EU-West-3 (Paris)","has_shared_plans":true},{"provider":"amazon-web-services","region":"eu-south-1","name":"Amazon Web Services - EU-South-1 (Milan)","has_shared_plans":false},{"provider":"amazon-web-services","region":"eu-south-2","name":"Amazon Web Services - EU-South-2 (Spain)","has_shared_plans":false},{"provider":"amazon-web-services","region":"eu-central-2","name":"Amazon Web Services - EU-Central-2 (Zurich)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ca-central-1","name":"Amazon Web Services - CA-Central-1 (Canada)","has_shared_plans":true},{"provider":"amazon-web-services","region":"ca-west-1","name":"Amazon Web Services - CA-West-1 (Calgary)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ap-southeast-1","name":"Amazon Web Services - AP-SouthEast-1 (Singapore)","has_shared_plans":true},{"provider":"amazon-web-services","region":"ap-southeast-2","name":"Amazon Web Services - AP-SouthEast-2 (Sydney)","has_shared_plans":true},{"provider":"amazon-web-services","region":"ap-southeast-3","name":"Amazon Web Services - AP-SouthEast-3 (Jakarta)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ap-southeast-4","name":"Amazon Web Services - AP-SouthEast-4 (Melbourne)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ap-southeast-5","name":"Amazon Web Services - AP-SouthEast-5 (Malaysia)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ap-southeast-6","name":"Amazon Web Services - AP-SouthEast-6 (New Zealand)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ap-southeast-7","name":"Amazon Web Services - AP-SouthEast-7 (Thailand)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ap-northeast-1","name":"Amazon Web Services - AP-NorthEast-1 (Tokyo)","has_shared_plans":true},{"provider":"amazon-web-services","region":"ap-northeast-2","name":"Amazon Web Services - AP-NorthEast-2 (Seoul)","has_shared_plans":true},{"provider":"amazon-web-services","region":"ap-northeast-3","name":"Amazon Web Services - AP-NorthEast-3 (Osaka)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ap-south-1","name":"Amazon Web Services - AP-South-1 (Mumbai)","has_shared_plans":true},{"provider":"amazon-web-services","region":"ap-south-2","name":"Amazon Web Services - AP-South-2 (Hyderabad)","has_shared_plans":false},{"provider":"amazon-web-services","region":"ap-east-1","name":"Amazon Web Services - AP-East-1 (Hong Kong)","has_shared_plans":true},{"provider":"amazon-web-services","region":"me-south-1","name":"Amazon Web Services - ME-South-1 (Bahrain)","has_shared_plans":true},{"provider":"amazon-web-services","region":"me-central-1","name":"Amazon Web Services - ME-Central-1 (UAE)","has_shared_plans":false},{"provider":"amazon-web-services","region":"af-south-1","name":"Amazon Web Services - AF-South-1 (Cape Town)","has_shared_plans":false},{"provider":"amazon-web-services","region":"il-central-1","name":"Amazon Web Services - IL-Central-1 (Tel Aviv)","has_shared_plans":false},{"provider":"amazon-web-services","region":"mx-central-1","name":"Amazon Web Services - MX-Central-1 (Mexico)","has_shared_plans":false},{"provider":"google-compute-engine","region":"us-central1","name":"Google Compute Engine - us-central1 (Iowa)","has_shared_plans":true},{"provider":"google-compute-engine","region":"us-east1","name":"Google Compute Engine - us-east1 (South Carolina)","has_shared_plans":true},{"provider":"google-compute-engine","region":"us-east4","name":"Google Compute Engine - us-east4 (North Virginia)","has_shared_plans":false},{"provider":"google-compute-engine","region":"us-east5","name":"Google Compute Engine - us-east5 (Columbus)","has_shared_plans":false},{"provider":"google-compute-engine","region":"us-west1","name":"Google Compute Engine - us-west1 (Oregon)","has_shared_plans":false},{"provider":"google-compute-engine","region":"us-west2","name":"Google Compute Engine - us-west2 (Los Angeles)","has_shared_plans":false},{"provider":"google-compute-engine","region":"us-west3","name":"Google Compute Engine - us-west3 (Salt Lake City)","has_shared_plans":false},{"provider":"google-compute-engine","region":"us-west4","name":"Google Compute Engine - us-west4 (Las Vegas)","has_shared_plans":false},{"provider":"google-compute-engine","region":"us-south1","name":"Google Compute Engine - us-south1 (Dallas, Texas)","has_shared_plans":false},{"provider":"google-compute-engine","region":"southamerica-east1","name":"Google Compute Engine - southamerica-east1 (São Paulo)","has_shared_plans":false},{"provider":"google-compute-engine","region":"southamerica-west1","name":"Google Compute Engine - southamerica-west1 (Santiago)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-north1","name":"Google Compute Engine - europe-north1 (Finland)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-north2","name":"Google Compute Engine - europe-north2 (Sweden)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-west1","name":"Google Compute Engine - europe-west1 (Belgium)","has_shared_plans":true},{"provider":"google-compute-engine","region":"europe-west2","name":"Google Compute Engine - europe-west2 (London)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-west3","name":"Google Compute Engine - europe-west3 (Frankfurt)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-west4","name":"Google Compute Engine - europe-west4 (Netherlands)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-west6","name":"Google Compute Engine - europe-west6 (Zürich)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-west8","name":"Google Compute Engine - europe-west8 (Milan)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-west9","name":"Google Compute Engine - europe-west9 (Paris)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-west10","name":"Google Compute Engine - europe-west10 (Berlin)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-west12","name":"Google Compute Engine - europe-west12 (Turin)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-central2","name":"Google Compute Engine - europe-central2 (Warsaw)","has_shared_plans":false},{"provider":"google-compute-engine","region":"europe-southwest1","name":"Google Compute Engine - europe-southwest1 (Madrid)","has_shared_plans":false},{"provider":"google-compute-engine","region":"me-west1","name":"Google Compute Engine - me-west1 (Tel Aviv)","has_shared_plans":false},{"provider":"google-compute-engine","region":"me-central1","name":"Google Compute Engine - me-central1 (Doha)","has_shared_plans":false},{"provider":"google-compute-engine","region":"me-central2","name":"Google Compute Engine - me-central2 (Dammam)","has_shared_plans":false},{"provider":"google-compute-engine","region":"asia-east1","name":"Google Compute Engine - asia-east1 (Taiwan)","has_shared_plans":true},{"provider":"google-compute-engine","region":"asia-east2","name":"Google Compute Engine - asia-east2 (Hong Kong)","has_shared_plans":false},{"provider":"google-compute-engine","region":"asia-northeast1","name":"Google Compute Engine - asia-northeast1 (Tokyo)","has_shared_plans":false},{"provider":"google-compute-engine","region":"asia-northeast2","name":"Google Compute Engine - asia-northeast2 (Osaka)","has_shared_plans":false},{"provider":"google-compute-engine","region":"asia-northeast3","name":"Google Compute Engine - asia-northeast3 (Seoul)","has_shared_plans":false},{"provider":"google-compute-engine","region":"asia-southeast1","name":"Google Compute Engine - asia-southeast1 (Singapore)","has_shared_plans":false},{"provider":"google-compute-engine","region":"asia-southeast2","name":"Google Compute Engine - asia-southeast2 (Jakarta)","has_shared_plans":false},{"provider":"google-compute-engine","region":"asia-south1","name":"Google Compute Engine - asia-south1 (Mumbai)","has_shared_plans":false},{"provider":"google-compute-engine","region":"asia-south2","name":"Google Compute Engine - asia-south2 (Delhi)","has_shared_plans":false},{"provider":"google-compute-engine","region":"australia-southeast1","name":"Google Compute Engine - australia-southeast1 (Sydney)","has_shared_plans":false},{"provider":"google-compute-engine","region":"australia-southeast2","name":"Google Compute Engine - australia-southeast2 (Melbourne)","has_shared_plans":false},{"provider":"google-compute-engine","region":"northamerica-northeast1","name":"Google Compute Engine - northamerica-northeast1 (Montréal, Canada)","has_shared_plans":false},{"provider":"google-compute-engine","region":"northamerica-northeast2","name":"Google Compute Engine - northamerica-northeast2 (Toronto, Canada)","has_shared_plans":false},{"provider":"digital-ocean","region":"nyc3","name":"DigitalOcean - New York 3","has_shared_plans":false},{"provider":"digital-ocean","region":"ams3","name":"DigitalOcean - Amsterdam 3","has_shared_plans":false},{"provider":"digital-ocean","region":"sgp1","name":"DigitalOcean - Singapore 1","has_shared_plans":false},{"provider":"digital-ocean","region":"lon1","name":"DigitalOcean - London 1","has_shared_plans":false},{"provider":"digital-ocean","region":"fra1","name":"DigitalOcean - Frankfurt 1","has_shared_plans":false},{"provider":"digital-ocean","region":"tor1","name":"DigitalOcean - Toronto 1","has_shared_plans":false},{"provider":"digital-ocean","region":"sfo3","name":"DigitalOcean - San Francisco 3","has_shared_plans":false},{"provider":"digital-ocean","region":"blr1","name":"DigitalOcean - Bangalore 1","has_shared_plans":false},{"provider":"digital-ocean","region":"syd1","name":"DigitalOcean - Sydney 1","has_shared_plans":false},{"provider":"azure-arm","region":"australiacentral","name":"Azure - Australia Central","has_shared_plans":false},{"provider":"azure-arm","region":"australiaeast","name":"Azure - Australia East","has_shared_plans":false},{"provider":"azure-arm","region":"australiasoutheast","name":"Azure - Australia Southeast","has_shared_plans":false},{"provider":"azure-arm","region":"brazilsouth","name":"Azure - Brazil South","has_shared_plans":false},{"provider":"azure-arm","region":"canadacentral","name":"Azure - Canada Central","has_shared_plans":false},{"provider":"azure-arm","region":"canadaeast","name":"Azure - Canada East","has_shared_plans":false},{"provider":"azure-arm","region":"centralindia","name":"Azure - Central India","has_shared_plans":false},{"provider":"azure-arm","region":"centralus","name":"Azure - Central US","has_shared_plans":false},{"provider":"azure-arm","region":"eastasia","name":"Azure - East Asia","has_shared_plans":false},{"provider":"azure-arm","region":"eastus","name":"Azure - East US","has_shared_plans":true},{"provider":"azure-arm","region":"eastus2","name":"Azure - East US 2","has_shared_plans":true},{"provider":"azure-arm","region":"francecentral","name":"Azure - France Central","has_shared_plans":false},{"provider":"azure-arm","region":"germanywestcentral","name":"Azure - Germany West Central","has_shared_plans":false},{"provider":"azure-arm","region":"indonesiacentral","name":"Azure - Indonesia Central","has_shared_plans":false},{"provider":"azure-arm","region":"israelcentral","name":"Azure - Israel Central","has_shared_plans":false},{"provider":"azure-arm","region":"italynorth","name":"Azure - Italy North","has_shared_plans":false},{"provider":"azure-arm","region":"japaneast","name":"Azure - Japan East","has_shared_plans":false},{"provider":"azure-arm","region":"japanwest","name":"Azure - Japan West","has_shared_plans":false},{"provider":"azure-arm","region":"koreacentral","name":"Azure - Korea Central","has_shared_plans":false},{"provider":"azure-arm","region":"koreasouth","name":"Azure - Korea South","has_shared_plans":false},{"provider":"azure-arm","region":"mexicocentral","name":"Azure - Mexico Central","has_shared_plans":false},{"provider":"azure-arm","region":"newzealandnorth","name":"Azure - New Zealand North","has_shared_plans":false},{"provider":"azure-arm","region":"northcentralus","name":"Azure - North Central US","has_shared_plans":false},{"provider":"azure-arm","region":"northeurope","name":"Azure - North Europe","has_shared_plans":false},{"provider":"azure-arm","region":"norwayeast","name":"Azure - Norway East","has_shared_plans":false},{"provider":"azure-arm","region":"polandcentral","name":"Azure - Poland Central","has_shared_plans":false},{"provider":"azure-arm","region":"qatarcentral","name":"Azure - Qatar Central","has_shared_plans":false},{"provider":"azure-arm","region":"southafricanorth","name":"Azure - South Africa North","has_shared_plans":false},{"provider":"azure-arm","region":"southcentralus","name":"Azure - South Central US","has_shared_plans":false},{"provider":"azure-arm","region":"southeastasia","name":"Azure - Southeast Asia","has_shared_plans":false},{"provider":"azure-arm","region":"southindia","name":"Azure - South India","has_shared_plans":false},{"provider":"azure-arm","region":"spaincentral","name":"Azure - Spain Central","has_shared_plans":false},{"provider":"azure-arm","region":"swedencentral","name":"Azure - Sweden Central","has_shared_plans":true},{"provider":"azure-arm","region":"switzerlandnorth","name":"Azure - Switzerland North","has_shared_plans":false},{"provider":"azure-arm","region":"uaenorth","name":"Azure - UAE North","has_shared_plans":false},{"provider":"azure-arm","region":"uksouth","name":"Azure - UK South","has_shared_plans":false},{"provider":"azure-arm","region":"ukwest","name":"Azure - UK West","has_shared_plans":false},{"provider":"azure-arm","region":"westcentralus","name":"Azure - West Central US","has_shared_plans":false},{"provider":"azure-arm","region":"westeurope","name":"Azure - West Europe","has_shared_plans":true},{"provider":"azure-arm","region":"westindia","name":"Azure - West India","has_shared_plans":false},{"provider":"azure-arm","region":"westus","name":"Azure - West US","has_shared_plans":true},{"provider":"azure-arm","region":"westus2","name":"Azure - West US 2","has_shared_plans":false},{"provider":"azure-arm","region":"westus3","name":"Azure - West US 3","has_shared_plans":false},{"provider":"scaleway","region":"nl-ams","name":"Scaleway - Amsterdam","has_shared_plans":true},{"provider":"scaleway","region":"fr-par","name":"Scaleway - Paris","has_shared_plans":true},{"provider":"scaleway","region":"pl-waw","name":"Scaleway - Warsaw","has_shared_plans":true}]' + headers: + Cache-Control: + - no-cache + Content-Length: + - "15983" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:13:02 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=UYKsnuWxUGZqcEJ65T3Hn8hL53RTgonxLqH3ufWgKIE%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101582"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=UYKsnuWxUGZqcEJ65T3Hn8hL53RTgonxLqH3ufWgKIE%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101582" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 1739111c-3e54-5edf-7f86-da0bb1d7eef9 + status: 200 OK + code: 200 + duration: 137.721027ms diff --git a/client/fixtures/list_vpcs.yaml b/client/fixtures/list_vpcs.yaml new file mode 100644 index 0000000..6166938 --- /dev/null +++ b/client/fixtures/list_vpcs.yaml @@ -0,0 +1,43 @@ +--- +version: 1 +interactions: + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/vpcs + method: GET + response: + body: '[]' + headers: + Cache-Control: + - no-cache + Content-Length: + - "2" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:13:03 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=dQAhEor6kU%2BZtt%2FoTKfG2D4ZerE3GLMhy78nLerGTHs%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764101583"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=dQAhEor6kU%2BZtt%2FoTKfG2D4ZerE3GLMhy78nLerGTHs%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764101583" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 6e1cc725-701a-eb03-fd0c-45e2110d4fed + status: 200 OK + code: 200 + duration: 149.518768ms diff --git a/client/fixtures/vpc_lifecycle.yaml b/client/fixtures/vpc_lifecycle.yaml new file mode 100644 index 0000000..1dad4ed --- /dev/null +++ b/client/fixtures/vpc_lifecycle.yaml @@ -0,0 +1,214 @@ +--- +version: 1 +interactions: + - request: + body: name=vcr-test-vpc®ion=amazon-web-services%3A%3Aus-east-1&subnet=10.56.72.0%2F24&tags%5B%5D=test&tags%5B%5D=vcr + form: + name: + - vcr-test-vpc + region: + - amazon-web-services::us-east-1 + subnet: + - 10.56.72.0/24 + tags[]: + - test + - vcr + headers: + Content-Type: + - application/x-www-form-urlencoded + url: https://customer.cloudamqp.com/api/vpcs + method: POST + response: + body: '{"apikey":"REDACTED","id":359566,"message":"VPC created"}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "85" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:21:40 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=3te6Rg6uRnnQj90TCGC8SF6EfiPh%2BFVqXkxrOp1qxz8%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102100"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=3te6Rg6uRnnQj90TCGC8SF6EfiPh%2BFVqXkxrOp1qxz8%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102100" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 4d0de6ee-712a-c415-d018-66c8d069a2ee + status: 200 OK + code: 200 + duration: 679.336045ms + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/vpcs/359566 + method: GET + response: + body: '{"id":359566,"instances":[],"name":"vcr-test-vpc","plan":"vpc","providerid":"e54c5767-bbbe-4b82-933b-cf099f83fa23","region":"amazon-web-services::us-east-1","subnet":"10.56.72.0/24","tags":["test","vcr"]}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "204" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:21:41 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=jhUQQW6tasu%2FkNtrVjymojbt78w4uT0QzADNxd3%2BKXY%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102101"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=jhUQQW6tasu%2FkNtrVjymojbt78w4uT0QzADNxd3%2BKXY%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102101" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - b7482f48-b8e4-5039-166c-99a23ec7af14 + status: 200 OK + code: 200 + duration: 208.162973ms + - request: + body: name=vcr-test-vpc-updated + form: + name: + - vcr-test-vpc-updated + headers: + Content-Type: + - application/x-www-form-urlencoded + url: https://customer.cloudamqp.com/api/vpcs/359566 + method: PUT + response: + body: '{"message":"Changes saved"}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "27" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:21:42 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=KMdMbPU0rYTdLTu2NzYqAVitX0cQYbLOxDrMaMJ7%2B28%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102102"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=KMdMbPU0rYTdLTu2NzYqAVitX0cQYbLOxDrMaMJ7%2B28%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102102" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - b589785c-223c-b9be-211c-02333a964bdd + status: 200 OK + code: 200 + duration: 197.510565ms + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/vpcs/359566 + method: GET + response: + body: '{"id":359566,"instances":[],"name":"vcr-test-vpc-updated","plan":"vpc","providerid":"e54c5767-bbbe-4b82-933b-cf099f83fa23","region":"amazon-web-services::us-east-1","subnet":"10.56.72.0/24","tags":[]}' + headers: + Cache-Control: + - no-cache + Content-Length: + - "200" + Content-Type: + - application/json + Date: + - Tue, 25 Nov 2025 20:21:42 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=KMdMbPU0rYTdLTu2NzYqAVitX0cQYbLOxDrMaMJ7%2B28%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102102"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=KMdMbPU0rYTdLTu2NzYqAVitX0cQYbLOxDrMaMJ7%2B28%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102102" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - d1bf5309-89f5-4064-9376-7b5a7932e07c + status: 200 OK + code: 200 + duration: 212.068024ms + - request: + body: "" + form: {} + headers: {} + url: https://customer.cloudamqp.com/api/vpcs/359566 + method: DELETE + response: + body: "" + headers: + Cache-Control: + - no-cache + Date: + - Tue, 25 Nov 2025 20:21:43 GMT + Nel: + - '{"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}' + - '{"report_to":"default","max_age":31536000,"include_subdomains":true}' + Referrer-Policy: + - strict-origin-when-cross-origin + Report-To: + - '{"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=ESfReqZM9TjW4Wvhi2D%2Bsi3GpXK9nwUgOYWesw3o1pc%3D\u0026sid=af571f24-03ee-46d1-9f90-ab9030c2c74c\u0026ts=1764102103"}],"max_age":3600}' + - '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://84codes.report-uri.com/a/t/g"}],"include_subdomains":true}' + Reporting-Endpoints: + - heroku-nel="https://nel.heroku.com/reports?s=ESfReqZM9TjW4Wvhi2D%2Bsi3GpXK9nwUgOYWesw3o1pc%3D&sid=af571f24-03ee-46d1-9f90-ab9030c2c74c&ts=1764102103" + Server: + - Heroku + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Via: + - 2.0 heroku-router + X-Content-Type-Options: + - nosniff + X-Request-Id: + - 2a304ea2-d04c-2e65-edfd-c99f663adc36 + status: 204 No Content + code: 204 + duration: 516.789911ms diff --git a/client/instances_bunny_vcr_test.go b/client/instances_bunny_vcr_test.go new file mode 100644 index 0000000..ecc2fcb --- /dev/null +++ b/client/instances_bunny_vcr_test.go @@ -0,0 +1,151 @@ +package client + +import ( + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v2/cassette" + "gopkg.in/dnaeon/go-vcr.v2/recorder" +) + +// TestCreateInstanceBunny1 tests creating an instance with bunny-1 plan +// This test uses VCR to record/replay HTTP interactions +func TestCreateInstanceBunny1(t *testing.T) { + r, err := recorder.New("fixtures/bunny1_create") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + // Add filter to sanitize sensitive data + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + // Get API key - only required if cassette doesn't exist (recording mode) + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + // If no API key and no cassette, skip test + if apiKey == "" { + // Use dummy key for replay mode (cassette intercepts all requests) + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + // Create instance with bunny-1 plan + req := &InstanceCreateRequest{ + Name: "bunny1-test", + Plan: "bunny-1", + Region: "amazon-web-services::us-east-1", + Tags: []string{"test", "bunny1"}, + } + + resp, err := client.CreateInstance(req) + + require.NoError(t, err) + require.NotNil(t, resp) + assert.NotZero(t, resp.ID) + assert.NotEmpty(t, resp.URL) + assert.NotEmpty(t, resp.APIKey) + + t.Logf("✓ Created bunny-1 instance with ID: %d", resp.ID) +} + +// TestUpdateInstanceBunny1ToHare1 tests updating an instance from bunny-1 to hare-1 +// Note: This test uses a pre-recorded cassette with a real instance update +func TestUpdateInstanceBunny1ToHare1(t *testing.T) { + r, err := recorder.New("fixtures/bunny1_to_hare1_update") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + // Add filter to sanitize sensitive data + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + // Get API key - only required if cassette doesn't exist (recording mode) + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + // If no API key and no cassette, skip test + if apiKey == "" { + // Use dummy key for replay mode (cassette intercepts all requests) + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + // Use a real instance ID from a bunny-1 instance + // This should be an instance that is already created and fully configured + instanceID := 359559 + + // Get instance before update + beforeUpdate, err := client.GetInstance(instanceID) + require.NoError(t, err) + require.NotNil(t, beforeUpdate) + t.Logf("Before update - Plan: %s", beforeUpdate.Plan) + + // Update to hare-1 + updateReq := &InstanceUpdateRequest{ + Plan: "hare-1", + } + + err = client.UpdateInstance(instanceID, updateReq) + require.NoError(t, err) + t.Logf("✓ Updated instance %d to hare-1", instanceID) + + // Note: Plan changes take time to reflect in the API + // The update is successful even though the plan may not show immediately + afterUpdate, err := client.GetInstance(instanceID) + if err == nil && afterUpdate != nil { + t.Logf("After update - Plan: %s (may take time to update)", afterUpdate.Plan) + } +} + +// TestDeleteInstanceBunny1 tests deleting a bunny-1 instance +func TestDeleteInstanceBunny1(t *testing.T) { + r, err := recorder.New("fixtures/bunny1_delete") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + // Add filter to sanitize sensitive data + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + // Get API key - only required if cassette doesn't exist (recording mode) + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + // If no API key and no cassette, skip test + if apiKey == "" { + // Use dummy key for replay mode (cassette intercepts all requests) + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + // Use an instance ID that exists (from a previous test or manual creation) + instanceID := 359559 + + // Delete the instance + err = client.DeleteInstance(instanceID) + require.NoError(t, err) + + t.Logf("✓ Deleted instance %d", instanceID) +} diff --git a/client/instances_vcr_test.go b/client/instances_vcr_test.go new file mode 100644 index 0000000..73e25f2 --- /dev/null +++ b/client/instances_vcr_test.go @@ -0,0 +1,108 @@ +package client + +import ( + "encoding/json" + "net/http" + "os" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/dnaeon/go-vcr.v2/cassette" + "gopkg.in/dnaeon/go-vcr.v2/recorder" +) + +// sanitizeResponseBody removes sensitive data from API responses +func sanitizeResponseBody(body string) string { + var data map[string]interface{} + if err := json.Unmarshal([]byte(body), &data); err != nil { + return body + } + + // Sanitize sensitive fields + if _, ok := data["apikey"]; ok { + data["apikey"] = "REDACTED" + } + + // Replace credentials in AMQP/AMQPS URLs + credRegex := regexp.MustCompile(`://([^:]+):([^@]+)@`) + + // Sanitize single URL field + if urlStr, ok := data["url"].(string); ok { + data["url"] = credRegex.ReplaceAllString(urlStr, "://REDACTED:REDACTED@") + } + + // Sanitize urls object (contains external/internal URLs) + if urls, ok := data["urls"].(map[string]interface{}); ok { + for key, val := range urls { + if urlStr, ok := val.(string); ok { + urls[key] = credRegex.ReplaceAllString(urlStr, "://REDACTED:REDACTED@") + } + } + } + + sanitized, err := json.Marshal(data) + if err != nil { + return body + } + + return string(sanitized) +} + +// TestCreateInstanceVCR tests the CreateInstance method using VCR to record/replay HTTP interactions +func TestCreateInstanceVCR(t *testing.T) { + // Create a VCR recorder + r, err := recorder.New("fixtures/create_instance") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + // Add filter to sanitize sensitive data in cassettes + r.AddFilter(func(i *cassette.Interaction) error { + // Sanitize Authorization header + delete(i.Request.Headers, "Authorization") + + // Sanitize sensitive data in response body (API keys, passwords) + i.Response.Body = sanitizeResponseBody(i.Response.Body) + + // Remove session cookies + delete(i.Response.Headers, "Set-Cookie") + + return nil + }) + + // Get API key - only required if cassette doesn't exist (recording mode) + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + // If no API key, use dummy (cassette will be used if it exists) + if apiKey == "" { + apiKey = "vcr-replay-mode" + } + + // Create HTTP client with VCR recorder as transport + httpClient := &http.Client{Transport: r} + + // Create client with VCR HTTP client + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + // Create instance request + req := &InstanceCreateRequest{ + Name: "vcr-test-instance", + Plan: "lemur", + Region: "amazon-web-services::us-east-1", + Tags: []string{"test", "vcr"}, + } + + // Execute the create instance request + resp, err := client.CreateInstance(req) + + // Verify the response + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.NotZero(t, resp.ID) + assert.NotEmpty(t, resp.URL) + assert.NotEmpty(t, resp.APIKey) + + t.Logf("Created instance with ID: %d", resp.ID) + t.Logf("Instance URL: %s", resp.URL) +} diff --git a/client/vpc_vcr_test.go b/client/vpc_vcr_test.go new file mode 100644 index 0000000..5f63dc0 --- /dev/null +++ b/client/vpc_vcr_test.go @@ -0,0 +1,118 @@ +package client + +import ( + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v2/cassette" + "gopkg.in/dnaeon/go-vcr.v2/recorder" +) + +// TestListVPCsVCR tests listing all VPCs +func TestListVPCsVCR(t *testing.T) { + r, err := recorder.New("fixtures/list_vpcs") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + if apiKey == "" { + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + vpcs, err := client.ListVPCs() + + require.NoError(t, err) + require.NotNil(t, vpcs) + t.Logf("✓ Listed %d VPCs", len(vpcs)) +} + +// TestVPCLifecycleVCR tests the complete VPC lifecycle: create, get, update, delete +func TestVPCLifecycleVCR(t *testing.T) { + r, err := recorder.New("fixtures/vpc_lifecycle") + if err != nil { + t.Fatal(err) + } + defer r.Stop() + + r.AddFilter(func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + i.Response.Body = sanitizeResponseBody(i.Response.Body) + delete(i.Response.Headers, "Set-Cookie") + return nil + }) + + apiKey := os.Getenv("CLOUDAMQP_APIKEY") + if apiKey == "" { + apiKey = "vcr-replay-mode" + } + + httpClient := &http.Client{Transport: r} + client := NewWithHTTPClient(apiKey, "https://customer.cloudamqp.com/api", httpClient) + + // Step 1: Create VPC + t.Log("Step 1: Creating VPC") + createReq := &VPCCreateRequest{ + Name: "vcr-test-vpc", + Region: "amazon-web-services::us-east-1", + Subnet: "10.56.72.0/24", + Tags: []string{"test", "vcr"}, + } + + createResp, err := client.CreateVPC(createReq) + require.NoError(t, err) + require.NotNil(t, createResp) + assert.NotZero(t, createResp.ID) + t.Logf("✓ Created VPC with ID: %d", createResp.ID) + + vpcID := createResp.ID + + // Step 2: Get VPC + t.Log("\nStep 2: Getting VPC details") + vpc, err := client.GetVPC(vpcID) + require.NoError(t, err) + require.NotNil(t, vpc) + assert.Equal(t, vpcID, vpc.ID) + assert.Equal(t, "vcr-test-vpc", vpc.Name) + t.Logf("✓ Got VPC: %s (subnet: %s)", vpc.Name, vpc.Subnet) + + // Step 3: Update VPC + t.Log("\nStep 3: Updating VPC") + updateReq := &VPCUpdateRequest{ + Name: "vcr-test-vpc-updated", + } + + err = client.UpdateVPC(vpcID, updateReq) + require.NoError(t, err) + t.Logf("✓ Updated VPC name") + + // Step 4: Get updated VPC + t.Log("\nStep 4: Verifying update") + updatedVPC, err := client.GetVPC(vpcID) + require.NoError(t, err) + require.NotNil(t, updatedVPC) + assert.Equal(t, "vcr-test-vpc-updated", updatedVPC.Name) + t.Logf("✓ Verified VPC name: %s", updatedVPC.Name) + + // Step 5: Delete VPC + t.Log("\nStep 5: Deleting VPC") + err = client.DeleteVPC(vpcID) + require.NoError(t, err) + t.Logf("✓ Deleted VPC with ID: %d", vpcID) + + t.Log("\n✓ VPC lifecycle test completed successfully!") +} diff --git a/go.mod b/go.mod index ee94633..03c10a0 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.25.3 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/cobra v1.10.1 // indirect github.com/spf13/pflag v1.0.9 // indirect @@ -12,5 +13,6 @@ require ( github.com/stretchr/testify v1.11.1 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/term v0.36.0 // indirect + gopkg.in/dnaeon/go-vcr.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7ce2be9..259222d 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -19,5 +21,7 @@ golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/dnaeon/go-vcr.v2 v2.3.0 h1:nwyjLPYlDmZkurnsEr5iWdjqy8kM+xV80E3TbvTA4Ow= +gopkg.in/dnaeon/go-vcr.v2 v2.3.0/go.mod h1:OgKb3ClaX2nN64BtvDFed3NIIEbB4jx1augFJq+IiYo= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=