// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package renderers import ( "maps" "slices" "testing" "github.com/google/go-cmp/cmp" "github.com/opentofu/opentofu/internal/command/jsonformat/computed" "github.com/opentofu/opentofu/internal/plans" ) type ValidateDiffFunction func(t *testing.T, diff computed.Diff) func validateDiff(t *testing.T, diff computed.Diff, expectedAction plans.Action, expectedReplace bool) { if diff.Replace != expectedReplace || diff.Action != expectedAction { t.Errorf("\nreplace:\n\texpected:%t\n\tactual:%t\naction:\n\texpected:%s\n\tactual:%s", expectedReplace, diff.Replace, expectedAction, diff.Action) } } func ValidatePrimitive(before, after interface{}, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) primitive, ok := diff.Renderer.(*primitiveRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } beforeDiff := cmp.Diff(primitive.before, before) afterDiff := cmp.Diff(primitive.after, after) if len(beforeDiff) > 0 || len(afterDiff) > 0 { t.Errorf("before diff: (%s), after diff: (%s)", beforeDiff, afterDiff) } } } func ValidateWriteOnly(action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) _, ok := diff.Renderer.(*writeOnlyRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } } } func ValidateObject(attributes map[string]ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) object, ok := diff.Renderer.(*objectRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } if !object.overrideNullSuffix { t.Errorf("created the wrong type of object renderer") } validateMapType(t, object.attributes, attributes) } } func ValidateNestedObject(attributes map[string]ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) object, ok := diff.Renderer.(*objectRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } if object.overrideNullSuffix { t.Errorf("created the wrong type of object renderer") } validateMapType(t, object.attributes, attributes) } } func ValidateMap(elements map[string]ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) m, ok := diff.Renderer.(*mapRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } validateMapType(t, m.elements, elements) } } func validateMapType(t *testing.T, actual map[string]computed.Diff, expected map[string]ValidateDiffFunction) { validateKeys(t, actual, expected) for key, expected := range expected { if actual, ok := actual[key]; ok { expected(t, actual) } } } func validateKeys[C, V any](t *testing.T, actual map[string]C, expected map[string]V) { gotKeys := slices.Sorted(maps.Keys(actual)) wantKeys := slices.Sorted(maps.Keys(expected)) if diff := cmp.Diff(wantKeys, gotKeys); len(diff) > 0 { t.Errorf("keys not match: %s", diff) } } func ValidateList(elements []ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) list, ok := diff.Renderer.(*listRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } if !list.displayContext { t.Errorf("created the wrong type of list renderer") } validateSliceType(t, list.elements, elements) } } func ValidateNestedList(elements []ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) list, ok := diff.Renderer.(*listRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } if list.displayContext { t.Errorf("created the wrong type of list renderer") } validateSliceType(t, list.elements, elements) } } func ValidateSet(elements []ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) set, ok := diff.Renderer.(*setRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } validateSliceType(t, set.elements, elements) } } func validateSliceType(t *testing.T, actual []computed.Diff, expected []ValidateDiffFunction) { if len(actual) != len(expected) { t.Errorf("expected %d elements but found %d elements", len(expected), len(actual)) return } for ix := 0; ix < len(expected); ix++ { expected[ix](t, actual[ix]) } } func ValidateBlock( attributes map[string]ValidateDiffFunction, singleBlocks map[string]ValidateDiffFunction, listBlocks map[string][]ValidateDiffFunction, mapBlocks map[string]map[string]ValidateDiffFunction, setBlocks map[string][]ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) block, ok := diff.Renderer.(*blockRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } validateKeys(t, block.attributes, attributes) validateKeys(t, block.blocks.SingleBlocks, singleBlocks) validateKeys(t, block.blocks.ListBlocks, listBlocks) validateKeys(t, block.blocks.MapBlocks, mapBlocks) validateKeys(t, block.blocks.SetBlocks, setBlocks) for key, expected := range attributes { if actual, ok := block.attributes[key]; ok { expected(t, actual) } } for key, expected := range singleBlocks { expected(t, block.blocks.SingleBlocks[key]) } for key, expected := range listBlocks { if actual, ok := block.blocks.ListBlocks[key]; ok { if len(actual) != len(expected) { t.Errorf("expected %d blocks within %s but found %d elements", len(expected), key, len(actual)) } for ix := range expected { expected[ix](t, actual[ix]) } } } for key, expected := range setBlocks { if actual, ok := block.blocks.SetBlocks[key]; ok { if len(actual) != len(expected) { t.Errorf("expected %d blocks within %s but found %d elements", len(expected), key, len(actual)) } for ix := range expected { expected[ix](t, actual[ix]) } } } for key, expected := range setBlocks { if actual, ok := block.blocks.SetBlocks[key]; ok { if len(actual) != len(expected) { t.Errorf("expected %d blocks within %s but found %d elements", len(expected), key, len(actual)) } for ix := range expected { expected[ix](t, actual[ix]) } } } for key, expected := range mapBlocks { if actual, ok := block.blocks.MapBlocks[key]; ok { if len(actual) != len(expected) { t.Errorf("expected %d blocks within %s but found %d elements", len(expected), key, len(actual)) } for dKey := range expected { expected[dKey](t, actual[dKey]) } } } } } func ValidateTypeChange(before, after ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) typeChange, ok := diff.Renderer.(*typeChangeRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } before(t, typeChange.before) after(t, typeChange.after) } } func ValidateSensitive(inner ValidateDiffFunction, beforeSensitive, afterSensitive bool, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) sensitive, ok := diff.Renderer.(*sensitiveRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } if beforeSensitive != sensitive.beforeSensitive || afterSensitive != sensitive.afterSensitive { 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) } inner(t, sensitive.inner) } } func ValidateUnknown(before ValidateDiffFunction, action plans.Action, replace bool) ValidateDiffFunction { return func(t *testing.T, diff computed.Diff) { validateDiff(t, diff, action, replace) unknown, ok := diff.Renderer.(*unknownRenderer) if !ok { t.Errorf("invalid renderer type: %T", diff.Renderer) return } if before == nil { if unknown.before.Renderer != nil { t.Errorf("did not expect a before renderer, but found one") } return } if unknown.before.Renderer == nil { t.Errorf("expected a before renderer, but found none") } before(t, unknown.before) } }