diff --git a/internal/command/views/cloud_test.go b/internal/command/views/cloud_test.go index 358a895bdd..63167fffdd 100644 --- a/internal/command/views/cloud_test.go +++ b/internal/command/views/cloud_test.go @@ -2,11 +2,11 @@ package views import ( "fmt" + "net/http" "strings" "testing" "time" - "github.com/google/go-cmp/cmp" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/terminal" @@ -14,6 +14,71 @@ import ( tfversion "github.com/hashicorp/terraform/version" ) +func TestNewCloudHuman_RetryLog(t *testing.T) { + t.Run("first retry, no output", func(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + newCloud := NewCloud(arguments.ViewHuman, NewView(streams).SetRunningInAutomation(true)) + cloudHuman, ok := newCloud.(*CloudHuman) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) + } + + cloudHuman.RetryLog(0, nil) + output := done(t).All() + if output != "" { + t.Fatalf("expected no output for the first retry attempt, but got: %s", output) + } + }) + + t.Run("second retry, server error", func(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + newCloud := NewCloud(arguments.ViewHuman, NewView(streams).SetRunningInAutomation(true)) + cloudHuman, ok := newCloud.(*CloudHuman) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) + } + + cloudHuman.RetryLog(1, &http.Response{StatusCode: 500}) + output := done(t).All() + expected := "There was an error connecting to HCP Terraform. Please do not exit\nTerraform to prevent data loss! Trying to restore the connection..." + if !strings.Contains(output, expected) { + t.Fatalf("expected output to contain: %s, but got %s", expected, output) + } + }) + + t.Run("subsequent retry with elapsed time", func(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + newCloud := NewCloud(arguments.ViewHuman, NewView(streams).SetRunningInAutomation(true)) + cloudHuman, ok := newCloud.(*CloudHuman) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) + } + + cloudHuman.lastRetry = time.Now().Add(-2 * time.Second) // Simulate a delay of 2 seconds + cloudHuman.RetryLog(2, &http.Response{StatusCode: 500}) + output := done(t).All() + expected := "Still trying to restore the connection... (2s elapsed)" + if !strings.Contains(output, expected) { + t.Fatalf("expected output to contain: %s, but got %s", expected, output) + } + }) + + t.Run("retry with 429 status, no output", func(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + newCloud := NewCloud(arguments.ViewHuman, NewView(streams).SetRunningInAutomation(true)) + cloudHuman, ok := newCloud.(*CloudHuman) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) + } + + cloudHuman.RetryLog(2, &http.Response{StatusCode: 429}) + output := done(t).All() + if output != "" { + t.Fatalf("expected no output for status code 429, but got: %s", output) + } + }) +} + func TestNewCloud_unsupportedViewDiagnostics(t *testing.T) { defer func() { r := recover() @@ -67,24 +132,6 @@ func TestNewCloud_humanViewOutput(t *testing.T) { }) } -func TestNewCloud_humanViewPrepareMessage(t *testing.T) { - t.Run("existing message code", func(t *testing.T) { - streams, _ := terminal.StreamsForTesting(t) - - newCloud := NewCloud(arguments.ViewHuman, NewView(streams).SetRunningInAutomation(true)) - if _, ok := newCloud.(*CloudHuman); !ok { - t.Fatalf("unexpected return type %t", newCloud) - } - - want := "\nThere was an error connecting to HCP Terraform. Please do not exit\nTerraform to prevent data loss! Trying to restore the connection..." - - actual := newCloud.PrepareMessage(InitialRetryErrorMessage) - if !cmp.Equal(want, actual) { - t.Errorf("unexpected output: %s", cmp.Diff(want, actual)) - } - }) -} - func TestNewCloud_humanViewDiagnostics(t *testing.T) { streams, done := terminal.StreamsForTesting(t) @@ -103,6 +150,126 @@ func TestNewCloud_humanViewDiagnostics(t *testing.T) { } } +func TestNewCloudJSON_RetryLog(t *testing.T) { + t.Run("attempt 0, no output", func(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + newCloud := NewCloud(arguments.ViewJSON, NewView(streams).SetRunningInAutomation(true)) + cloudJSON, ok := newCloud.(*CloudJSON) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) + } + + cloudJSON.RetryLog(0, nil) + + version := tfversion.String() + want := []map[string]interface{}{ + { + "@level": "info", + "@message": fmt.Sprintf("Terraform %s", version), + "@module": "terraform.ui", + "terraform": version, + "type": "version", + "ui": JSON_UI_VERSION, + }, + } + actual := done(t).Stdout() + testJSONViewOutputEqualsFull(t, actual, want) + }) + + t.Run("attempt 1, server error", func(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + newCloud := NewCloud(arguments.ViewJSON, NewView(streams).SetRunningInAutomation(true)) + cloudJSON, ok := newCloud.(*CloudJSON) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) + } + + cloudJSON.RetryLog(1, &http.Response{StatusCode: 500}) + + version := tfversion.String() + want := []map[string]interface{}{ + { + "@level": "info", + "@message": fmt.Sprintf("Terraform %s", version), + "@module": "terraform.ui", + "terraform": version, + "type": "version", + "ui": JSON_UI_VERSION, + }, + { + "@level": "info", + "@message": "There was an error connecting to HCP Terraform. Please do not exit\nTerraform to prevent data loss! Trying to restore the connection...", + "message_code": "initial_retry_error_message", + "@module": "terraform.ui", + "type": "cloud_output", + }, + } + actual := done(t).Stdout() + testJSONViewOutputEqualsFull(t, actual, want) + }) + + t.Run("subsequent retry with elapsed time", func(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + newCloud := NewCloud(arguments.ViewJSON, NewView(streams).SetRunningInAutomation(true)) + cloudJSON, ok := newCloud.(*CloudJSON) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) + } + + cloudJSON.lastRetry = time.Now().Add(-2 * time.Second) // Simulate a delay of 2 seconds + cloudJSON.RetryLog(2, &http.Response{StatusCode: 500}) + + version := tfversion.String() + want := []map[string]interface{}{ + { + "@level": "info", + "@message": fmt.Sprintf("Terraform %s", version), + "@module": "terraform.ui", + "terraform": version, + "type": "version", + "ui": JSON_UI_VERSION, + }, + { + "@level": "info", + "@message": "Still trying to restore the connection... (2s elapsed)", + "message_code": "repeated_retry_error_message", + "@module": "terraform.ui", + "type": "cloud_output", + }, + } + + actual := done(t).Stdout() + testJSONViewOutputEqualsFull(t, actual, want) + }) + + t.Run("retry with 429 status, no output", func(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + newCloud := NewCloud(arguments.ViewJSON, NewView(streams).SetRunningInAutomation(true)) + cloudJSON, ok := newCloud.(*CloudJSON) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) + } + + cloudJSON.RetryLog(0, &http.Response{ + StatusCode: http.StatusTooManyRequests, // HTTP: 429 + }) + + version := tfversion.String() + want := []map[string]interface{}{ + { + "@level": "info", + "@message": fmt.Sprintf("Terraform %s", version), + "@module": "terraform.ui", + "terraform": version, + "type": "version", + "ui": JSON_UI_VERSION, + }, + } + actual := done(t).Stdout() + testJSONViewOutputEqualsFull(t, actual, want) + }) +} + func TestNewCloud_jsonViewOutput(t *testing.T) { t.Run("no param", func(t *testing.T) { streams, done := terminal.StreamsForTesting(t) @@ -172,24 +339,6 @@ func TestNewCloud_jsonViewOutput(t *testing.T) { }) } -func TestNewCloud_jsonViewPrepareMessage(t *testing.T) { - t.Run("existing message code", func(t *testing.T) { - streams, _ := terminal.StreamsForTesting(t) - - newCloud := NewCloud(arguments.ViewJSON, NewView(streams).SetRunningInAutomation(true)) - if _, ok := newCloud.(*CloudJSON); !ok { - t.Fatalf("unexpected return type %t", newCloud) - } - - want := "There was an error connecting to HCP Terraform. Please do not exit\nTerraform to prevent data loss! Trying to restore the connection..." - - actual := newCloud.PrepareMessage(InitialRetryErrorMessage) - if !cmp.Equal(want, actual) { - t.Errorf("unexpected output: %s", cmp.Diff(want, actual)) - } - }) -} - func TestNewCloud_jsonViewDiagnostics(t *testing.T) { streams, done := terminal.StreamsForTesting(t)