github.com/opentofu/opentofu@v1.7.1/internal/cloud/backend_taskStage_taskResults_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package cloud 7 8 import ( 9 "context" 10 "strings" 11 "testing" 12 13 "github.com/hashicorp/go-tfe" 14 ) 15 16 type testIntegrationOutput struct { 17 ctx *IntegrationContext 18 output *strings.Builder 19 t *testing.T 20 } 21 22 var _ IntegrationOutputWriter = (*testIntegrationOutput)(nil) // Compile time check 23 24 func (s *testIntegrationOutput) End() { 25 s.output.WriteString("END\n") 26 } 27 28 func (s *testIntegrationOutput) SubOutput(str string) { 29 s.output.WriteString(s.ctx.B.Colorize().Color("[reset]│ "+str) + "\n") 30 } 31 32 func (s *testIntegrationOutput) Output(str string) { 33 s.output.WriteString(s.ctx.B.Colorize().Color("[reset]│ ") + str + "\n") 34 } 35 36 func (s *testIntegrationOutput) OutputElapsed(message string, maxMessage int) { 37 s.output.WriteString("PENDING MESSAGE: " + message) 38 } 39 40 func newMockIntegrationContext(b *Cloud, t *testing.T) (*IntegrationContext, *testIntegrationOutput) { 41 ctx := context.Background() 42 43 // Retrieve the workspace used to run this operation in. 44 w, err := b.client.Workspaces.Read(ctx, b.organization, b.WorkspaceMapping.Name) 45 if err != nil { 46 t.Fatalf("error retrieving workspace: %v", err) 47 } 48 49 // Create a new configuration version. 50 c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{}) 51 if err != nil { 52 t.Fatalf("error creating configuration version: %v", err) 53 } 54 55 // Create a pending run to block this run. 56 r, err := b.client.Runs.Create(ctx, tfe.RunCreateOptions{ 57 ConfigurationVersion: c, 58 Workspace: w, 59 }) 60 if err != nil { 61 t.Fatalf("error creating pending run: %v", err) 62 } 63 64 op, configCleanup, done := testOperationPlan(t, "./testdata/plan") 65 defer configCleanup() 66 defer done(t) 67 68 integrationContext := &IntegrationContext{ 69 B: b, 70 StopContext: ctx, 71 CancelContext: ctx, 72 Op: op, 73 Run: r, 74 } 75 76 return integrationContext, &testIntegrationOutput{ 77 ctx: integrationContext, 78 output: &strings.Builder{}, 79 t: t, 80 } 81 } 82 83 func TestCloud_runTasksWithTaskResults(t *testing.T) { 84 b, bCleanup := testBackendWithName(t) 85 defer bCleanup() 86 87 integrationContext, writer := newMockIntegrationContext(b, t) 88 89 cases := map[string]struct { 90 taskStage func() *tfe.TaskStage 91 context *IntegrationContext 92 writer *testIntegrationOutput 93 expectedOutputs []string 94 isError bool 95 }{ 96 "all-succeeded": { 97 taskStage: func() *tfe.TaskStage { 98 ts := &tfe.TaskStage{} 99 ts.TaskResults = []*tfe.TaskResult{ 100 {ID: "1", TaskName: "Mandatory", Message: "A-OK", Status: "passed", WorkspaceTaskEnforcementLevel: "mandatory"}, 101 {ID: "2", TaskName: "Advisory", Message: "A-OK", Status: "passed", WorkspaceTaskEnforcementLevel: "advisory"}, 102 } 103 return ts 104 }, 105 writer: writer, 106 context: integrationContext, 107 expectedOutputs: []string{"Overall Result: Passed\n"}, 108 isError: false, 109 }, 110 "mandatory-failed": { 111 taskStage: func() *tfe.TaskStage { 112 ts := &tfe.TaskStage{} 113 ts.TaskResults = []*tfe.TaskResult{ 114 {ID: "1", TaskName: "Mandatory", Message: "500 Error", Status: "failed", WorkspaceTaskEnforcementLevel: "mandatory"}, 115 {ID: "2", TaskName: "Advisory", Message: "A-OK", Status: "passed", WorkspaceTaskEnforcementLevel: "advisory"}, 116 } 117 return ts 118 }, 119 writer: writer, 120 context: integrationContext, 121 expectedOutputs: []string{"Passed\n", "A-OK\n", "Overall Result: Failed\n"}, 122 isError: true, 123 }, 124 "advisory-failed": { 125 taskStage: func() *tfe.TaskStage { 126 ts := &tfe.TaskStage{} 127 ts.TaskResults = []*tfe.TaskResult{ 128 {ID: "1", TaskName: "Mandatory", Message: "A-OK", Status: "passed", WorkspaceTaskEnforcementLevel: "mandatory"}, 129 {ID: "2", TaskName: "Advisory", Message: "500 Error", Status: "failed", WorkspaceTaskEnforcementLevel: "advisory"}, 130 } 131 return ts 132 }, 133 writer: writer, 134 context: integrationContext, 135 expectedOutputs: []string{"Failed (Advisory)", "Overall Result: Passed with advisory failure"}, 136 isError: false, 137 }, 138 "unreachable": { 139 taskStage: func() *tfe.TaskStage { 140 ts := &tfe.TaskStage{} 141 ts.TaskResults = []*tfe.TaskResult{ 142 {ID: "1", TaskName: "Mandatory", Message: "", Status: "unreachable", WorkspaceTaskEnforcementLevel: "mandatory"}, 143 {ID: "2", TaskName: "Advisory", Message: "", Status: "unreachable", WorkspaceTaskEnforcementLevel: "advisory"}, 144 } 145 return ts 146 }, 147 writer: writer, 148 context: integrationContext, 149 expectedOutputs: []string{"Skipping"}, 150 isError: false, 151 }, 152 } 153 154 for _, c := range cases { 155 c.writer.output.Reset() 156 trs := taskResultSummarizer{ 157 cloud: b, 158 } 159 c.context.Poll(0, 0, func(i int) (bool, error) { 160 cont, _, _ := trs.Summarize(c.context, c.writer, c.taskStage()) 161 if cont { 162 return true, nil 163 } 164 165 output := c.writer.output.String() 166 for _, expected := range c.expectedOutputs { 167 if !strings.Contains(output, expected) { 168 t.Fatalf("Expected output to contain '%s' but it was:\n\n%s", expected, output) 169 } 170 } 171 return false, nil 172 }) 173 } 174 }