| package renderers |
| |
| import ( |
| "sort" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| |
| "github.com/hashicorp/terraform/internal/command/jsonformat/computed" |
| "github.com/hashicorp/terraform/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 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) { |
| if len(actual) != len(expected) { |
| |
| var actualAttributes []string |
| var expectedAttributes []string |
| |
| for key := range actual { |
| actualAttributes = append(actualAttributes, key) |
| } |
| for key := range expected { |
| expectedAttributes = append(expectedAttributes, key) |
| } |
| |
| sort.Strings(actualAttributes) |
| sort.Strings(expectedAttributes) |
| |
| if diff := cmp.Diff(actualAttributes, expectedAttributes); len(diff) > 0 { |
| t.Errorf("actual and expected attributes did 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) |
| } |
| } |