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  }