mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org> Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
323 lines
9.0 KiB
Go
323 lines
9.0 KiB
Go
// 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)
|
|
}
|
|
}
|