github.com/opentofu/opentofu@v1.7.1/internal/states/statefile/roundtrip_test.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 statefile
     7  
     8  import (
     9  	"bytes"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/go-test/deep"
    17  	"github.com/opentofu/opentofu/internal/encryption"
    18  	"github.com/opentofu/opentofu/internal/encryption/enctest"
    19  )
    20  
    21  func TestRoundtrip(t *testing.T) {
    22  	const dir = "testdata/roundtrip"
    23  	entries, err := os.ReadDir(dir)
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  
    28  	for _, info := range entries {
    29  		const inSuffix = ".in.tfstate"
    30  		const outSuffix = ".out.tfstate"
    31  
    32  		if info.IsDir() {
    33  			continue
    34  		}
    35  		inName := info.Name()
    36  		if !strings.HasSuffix(inName, inSuffix) {
    37  			continue
    38  		}
    39  		name := inName[:len(inName)-len(inSuffix)]
    40  		outName := name + outSuffix
    41  
    42  		t.Run(name, func(t *testing.T) {
    43  			oSrcWant, err := os.ReadFile(filepath.Join(dir, outName))
    44  			if err != nil {
    45  				t.Fatal(err)
    46  			}
    47  			oWant, diags := readStateV4(oSrcWant)
    48  			if diags.HasErrors() {
    49  				t.Fatal(diags.Err())
    50  			}
    51  
    52  			ir, err := os.Open(filepath.Join(dir, inName))
    53  			if err != nil {
    54  				t.Fatal(err)
    55  			}
    56  			defer ir.Close()
    57  
    58  			f, err := Read(ir, encryption.StateEncryptionDisabled())
    59  			if err != nil {
    60  				t.Fatalf("unexpected error: %s", err)
    61  			}
    62  
    63  			var buf bytes.Buffer
    64  			err = Write(f, &buf, encryption.StateEncryptionDisabled())
    65  			if err != nil {
    66  				t.Fatal(err)
    67  			}
    68  			oSrcWritten := buf.Bytes()
    69  
    70  			oGot, diags := readStateV4(oSrcWritten)
    71  			if diags.HasErrors() {
    72  				t.Fatal(diags.Err())
    73  			}
    74  
    75  			problems := deep.Equal(oGot, oWant)
    76  			sort.Strings(problems)
    77  			for _, problem := range problems {
    78  				t.Error(problem)
    79  			}
    80  		})
    81  	}
    82  }
    83  
    84  func TestRoundtripEncryption(t *testing.T) {
    85  	const path = "testdata/roundtrip/v4-modules.out.tfstate"
    86  
    87  	enc := enctest.EncryptionWithFallback().State()
    88  
    89  	unencryptedInput, err := os.Open(path)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	defer unencryptedInput.Close()
    94  
    95  	// Read unencrypted using fallback
    96  	originalState, err := Read(unencryptedInput, enc)
    97  	if err != nil {
    98  		t.Fatalf("unexpected error: %s", err)
    99  	}
   100  
   101  	// Write encrypted
   102  	var encrypted bytes.Buffer
   103  	err = Write(originalState, &encrypted, enc)
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	// Make sure it is encrypted / not readable
   109  	encryptedCopy := encrypted
   110  	_, err = Read(&encryptedCopy, encryption.StateEncryptionDisabled())
   111  	if err == nil || err.Error() != "Unsupported state file format: This state file is encrypted and can not be read without an encryption configuration" {
   112  		t.Fatalf("expected written state file to be encrypted!")
   113  	}
   114  
   115  	// Read encrypted
   116  	newState, err := Read(&encrypted, enc)
   117  	if err != nil {
   118  		t.Fatalf("unexpected error: %s", err)
   119  	}
   120  
   121  	// Compare before/after encryption workflow
   122  	problems := deep.Equal(newState, originalState)
   123  	sort.Strings(problems)
   124  	for _, problem := range problems {
   125  		t.Error(problem)
   126  	}
   127  }