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,16 +150,41 @@ func TestNewCloud_humanViewDiagnostics(t *testing.T) { } } -func TestNewCloud_jsonViewOutput(t *testing.T) { - t.Run("no param", func(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)) - if _, ok := newCloud.(*CloudJSON); !ok { - t.Fatalf("unexpected return type %t", newCloud) + cloudJSON, ok := newCloud.(*CloudJSON) + if !ok { + t.Fatalf("unexpected return type %T", newCloud) } - newCloud.Output(InitialRetryErrorMessage) + cloudJSON.RetryLog(1, &http.Response{StatusCode: 500}) version := tfversion.String() want := []map[string]interface{}{ @@ -132,12 +204,74 @@ func TestNewCloud_jsonViewOutput(t *testing.T) { "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("single param", func(t *testing.T) { + 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) newCloud := NewCloud(arguments.ViewJSON, NewView(streams).SetRunningInAutomation(true)) @@ -145,8 +279,7 @@ func TestNewCloud_jsonViewOutput(t *testing.T) { t.Fatalf("unexpected return type %t", newCloud) } - duration := 5 * time.Second - newCloud.Output(RepeatedRetryErrorMessage, duration) + newCloud.Output(InitialRetryErrorMessage) version := tfversion.String() want := []map[string]interface{}{ @@ -160,9 +293,9 @@ func TestNewCloud_jsonViewOutput(t *testing.T) { }, { "@level": "info", - "@message": fmt.Sprintf("Still trying to restore the connection... (%s elapsed)", duration), + "@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", - "message_code": "repeated_retry_error_message", "type": "cloud_output", }, } @@ -170,23 +303,39 @@ func TestNewCloud_jsonViewOutput(t *testing.T) { actual := done(t).Stdout() testJSONViewOutputEqualsFull(t, actual, want) }) -} -func TestNewCloud_jsonViewPrepareMessage(t *testing.T) { - t.Run("existing message code", func(t *testing.T) { - streams, _ := terminal.StreamsForTesting(t) + t.Run("single param", func(t *testing.T) { + streams, done := 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..." + duration := 5 * time.Second + newCloud.Output(RepeatedRetryErrorMessage, duration) - actual := newCloud.PrepareMessage(InitialRetryErrorMessage) - if !cmp.Equal(want, actual) { - t.Errorf("unexpected output: %s", cmp.Diff(want, actual)) + 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": fmt.Sprintf("Still trying to restore the connection... (%s elapsed)", duration), + "@module": "terraform.ui", + "message_code": "repeated_retry_error_message", + "type": "cloud_output", + }, } + + actual := done(t).Stdout() + testJSONViewOutputEqualsFull(t, actual, want) }) }