github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/computed/renderers/testing.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 renderers 7 8 import ( 9 "sort" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 14 "github.com/opentofu/opentofu/internal/command/jsonformat/computed" 15 "github.com/opentofu/opentofu/internal/plans" 16 ) 17 18 type ValidateDiffFunction func(t *testing.T, diff computed.Diff) 19 20 func validateDiff(t *testing.T, diff computed.Diff, expectedAction plans.Action, expectedReplace bool) { 21 if diff.Replace != expectedReplace || diff.Action != expectedAction { 22 t.Errorf("\nreplace:\n\texpected:%t\n\tactual:%t\naction:\n\texpected:%s\n\tactual:%s", expectedReplace, diff.Replace, expectedAction, diff.Action) 23 } 24 } 25 26 func ValidatePrimitive(before, after interface{}, action plans.Action, replace bool) ValidateDiffFunction { 27 return func(t *testing.T, diff computed.Diff) { 28 validateDiff(t, diff, action, replace) 29 30 primitive, ok := diff.Renderer.(*primitiveRenderer) 31 if !ok { 32 t.Errorf("invalid renderer type: %T", diff.Renderer) 33 return 34 } 35 36 beforeDiff := cmp.Diff(primitive.before, before) 37 afterDiff := cmp.Diff(primitive.after, after) 38 39 if len(beforeDiff) > 0 || len(afterDiff) > 0 { 40 t.Errorf("before diff: (%s), after diff: (%s)", beforeDiff, afterDiff) 41 } 42 } 43 } 44 45 func ValidateObject(attributes map[string]ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { 46 return func(t *testing.T, diff computed.Diff) { 47 validateDiff(t, diff, action, replace) 48 49 object, ok := diff.Renderer.(*objectRenderer) 50 if !ok { 51 t.Errorf("invalid renderer type: %T", diff.Renderer) 52 return 53 } 54 55 if !object.overrideNullSuffix { 56 t.Errorf("created the wrong type of object renderer") 57 } 58 59 validateMapType(t, object.attributes, attributes) 60 } 61 } 62 63 func ValidateNestedObject(attributes map[string]ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { 64 return func(t *testing.T, diff computed.Diff) { 65 validateDiff(t, diff, action, replace) 66 67 object, ok := diff.Renderer.(*objectRenderer) 68 if !ok { 69 t.Errorf("invalid renderer type: %T", diff.Renderer) 70 return 71 } 72 73 if object.overrideNullSuffix { 74 t.Errorf("created the wrong type of object renderer") 75 } 76 77 validateMapType(t, object.attributes, attributes) 78 } 79 } 80 81 func ValidateMap(elements map[string]ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { 82 return func(t *testing.T, diff computed.Diff) { 83 validateDiff(t, diff, action, replace) 84 85 m, ok := diff.Renderer.(*mapRenderer) 86 if !ok { 87 t.Errorf("invalid renderer type: %T", diff.Renderer) 88 return 89 } 90 91 validateMapType(t, m.elements, elements) 92 } 93 } 94 95 func validateMapType(t *testing.T, actual map[string]computed.Diff, expected map[string]ValidateDiffFunction) { 96 validateKeys(t, actual, expected) 97 98 for key, expected := range expected { 99 if actual, ok := actual[key]; ok { 100 expected(t, actual) 101 } 102 } 103 } 104 105 func validateKeys[C, V any](t *testing.T, actual map[string]C, expected map[string]V) { 106 if len(actual) != len(expected) { 107 108 var actualAttributes []string 109 var expectedAttributes []string 110 111 for key := range actual { 112 actualAttributes = append(actualAttributes, key) 113 } 114 for key := range expected { 115 expectedAttributes = append(expectedAttributes, key) 116 } 117 118 sort.Strings(actualAttributes) 119 sort.Strings(expectedAttributes) 120 121 if diff := cmp.Diff(actualAttributes, expectedAttributes); len(diff) > 0 { 122 t.Errorf("actual and expected attributes did not match: %s", diff) 123 } 124 } 125 } 126 127 func ValidateList(elements []ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { 128 return func(t *testing.T, diff computed.Diff) { 129 validateDiff(t, diff, action, replace) 130 131 list, ok := diff.Renderer.(*listRenderer) 132 if !ok { 133 t.Errorf("invalid renderer type: %T", diff.Renderer) 134 return 135 } 136 137 if !list.displayContext { 138 t.Errorf("created the wrong type of list renderer") 139 } 140 141 validateSliceType(t, list.elements, elements) 142 } 143 } 144 145 func ValidateNestedList(elements []ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { 146 return func(t *testing.T, diff computed.Diff) { 147 validateDiff(t, diff, action, replace) 148 149 list, ok := diff.Renderer.(*listRenderer) 150 if !ok { 151 t.Errorf("invalid renderer type: %T", diff.Renderer) 152 return 153 } 154 155 if list.displayContext { 156 t.Errorf("created the wrong type of list renderer") 157 } 158 159 validateSliceType(t, list.elements, elements) 160 } 161 } 162 163 func ValidateSet(elements []ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { 164 return func(t *testing.T, diff computed.Diff) { 165 validateDiff(t, diff, action, replace) 166 167 set, ok := diff.Renderer.(*setRenderer) 168 if !ok { 169 t.Errorf("invalid renderer type: %T", diff.Renderer) 170 return 171 } 172 173 validateSliceType(t, set.elements, elements) 174 } 175 } 176 177 func validateSliceType(t *testing.T, actual []computed.Diff, expected []ValidateDiffFunction) { 178 if len(actual) != len(expected) { 179 t.Errorf("expected %d elements but found %d elements", len(expected), len(actual)) 180 return 181 } 182 183 for ix := 0; ix < len(expected); ix++ { 184 expected[ix](t, actual[ix]) 185 } 186 } 187 188 func ValidateBlock( 189 attributes map[string]ValidateDiffFunction, 190 singleBlocks map[string]ValidateDiffFunction, 191 listBlocks map[string][]ValidateDiffFunction, 192 mapBlocks map[string]map[string]ValidateDiffFunction, 193 setBlocks map[string][]ValidateDiffFunction, 194 action plans.Action, 195 replace bool) ValidateDiffFunction { 196 return func(t *testing.T, diff computed.Diff) { 197 validateDiff(t, diff, action, replace) 198 199 block, ok := diff.Renderer.(*blockRenderer) 200 if !ok { 201 t.Errorf("invalid renderer type: %T", diff.Renderer) 202 return 203 } 204 205 validateKeys(t, block.attributes, attributes) 206 validateKeys(t, block.blocks.SingleBlocks, singleBlocks) 207 validateKeys(t, block.blocks.ListBlocks, listBlocks) 208 validateKeys(t, block.blocks.MapBlocks, mapBlocks) 209 validateKeys(t, block.blocks.SetBlocks, setBlocks) 210 211 for key, expected := range attributes { 212 if actual, ok := block.attributes[key]; ok { 213 expected(t, actual) 214 } 215 } 216 217 for key, expected := range singleBlocks { 218 expected(t, block.blocks.SingleBlocks[key]) 219 } 220 221 for key, expected := range listBlocks { 222 if actual, ok := block.blocks.ListBlocks[key]; ok { 223 if len(actual) != len(expected) { 224 t.Errorf("expected %d blocks within %s but found %d elements", len(expected), key, len(actual)) 225 } 226 for ix := range expected { 227 expected[ix](t, actual[ix]) 228 } 229 } 230 } 231 232 for key, expected := range setBlocks { 233 if actual, ok := block.blocks.SetBlocks[key]; ok { 234 if len(actual) != len(expected) { 235 t.Errorf("expected %d blocks within %s but found %d elements", len(expected), key, len(actual)) 236 } 237 for ix := range expected { 238 expected[ix](t, actual[ix]) 239 } 240 } 241 } 242 243 for key, expected := range setBlocks { 244 if actual, ok := block.blocks.SetBlocks[key]; ok { 245 if len(actual) != len(expected) { 246 t.Errorf("expected %d blocks within %s but found %d elements", len(expected), key, len(actual)) 247 } 248 for ix := range expected { 249 expected[ix](t, actual[ix]) 250 } 251 } 252 } 253 254 for key, expected := range mapBlocks { 255 if actual, ok := block.blocks.MapBlocks[key]; ok { 256 if len(actual) != len(expected) { 257 t.Errorf("expected %d blocks within %s but found %d elements", len(expected), key, len(actual)) 258 } 259 for dKey := range expected { 260 expected[dKey](t, actual[dKey]) 261 } 262 } 263 } 264 } 265 } 266 267 func ValidateTypeChange(before, after ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { 268 return func(t *testing.T, diff computed.Diff) { 269 validateDiff(t, diff, action, replace) 270 271 typeChange, ok := diff.Renderer.(*typeChangeRenderer) 272 if !ok { 273 t.Errorf("invalid renderer type: %T", diff.Renderer) 274 return 275 } 276 277 before(t, typeChange.before) 278 after(t, typeChange.after) 279 } 280 } 281 282 func ValidateSensitive(inner ValidateDiffFunction, beforeSensitive, afterSensitive bool, action plans.Action, replace bool) ValidateDiffFunction { 283 return func(t *testing.T, diff computed.Diff) { 284 validateDiff(t, diff, action, replace) 285 286 sensitive, ok := diff.Renderer.(*sensitiveRenderer) 287 if !ok { 288 t.Errorf("invalid renderer type: %T", diff.Renderer) 289 return 290 } 291 292 if beforeSensitive != sensitive.beforeSensitive || afterSensitive != sensitive.afterSensitive { 293 t.Errorf("before or after sensitive values don't match:\n\texpected; before: %t after: %t\n\tactual; before: %t, after: %t", beforeSensitive, afterSensitive, sensitive.beforeSensitive, sensitive.afterSensitive) 294 } 295 296 inner(t, sensitive.inner) 297 } 298 } 299 300 func ValidateUnknown(before ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { 301 return func(t *testing.T, diff computed.Diff) { 302 validateDiff(t, diff, action, replace) 303 304 unknown, ok := diff.Renderer.(*unknownRenderer) 305 if !ok { 306 t.Errorf("invalid renderer type: %T", diff.Renderer) 307 return 308 } 309 310 if before == nil { 311 if unknown.before.Renderer != nil { 312 t.Errorf("did not expect a before renderer, but found one") 313 } 314 return 315 } 316 317 if unknown.before.Renderer == nil { 318 t.Errorf("expected a before renderer, but found none") 319 } 320 321 before(t, unknown.before) 322 } 323 }