mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-04 13:17:43 -06:00
8330b7295b
* change -> diff, value -> change * also update readme# * pause * Update internal/command/jsonformat/computed/diff.go Co-authored-by: Alisdair McDiarmid <alisdair@users.noreply.github.com> * add interface assertions for diff renderers * Add support for different kinds of blocks, and for sensitive blocks Co-authored-by: Alisdair McDiarmid <alisdair@users.noreply.github.com>
319 lines
8.9 KiB
Go
319 lines
8.9 KiB
Go
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)
|
|
}
|
|
}
|