Skip to content

Commit 0943288

Browse files
committed
WIP - updating tests, currently blocked
1 parent f527f4f commit 0943288

File tree

2 files changed

+156
-89
lines changed

2 files changed

+156
-89
lines changed

internal/command/meta_backend_test.go

Lines changed: 139 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2081,82 +2081,154 @@ func Test_determineInitReason(t *testing.T) {
20812081
}
20822082

20832083
// Newly configured state store
2084-
//
2085-
// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch
2086-
// case for this scenario, and will need to be updated when that init feature is implemented.
2084+
// Working directory has state_store in config but no preexisting backend state file
20872085
func TestMetaBackend_configureNewStateStore(t *testing.T) {
2088-
td := t.TempDir()
2089-
testCopyDir(t, testFixturePath("state-store-new"), td)
2090-
t.Chdir(td)
2086+
cases := map[string]struct {
2087+
// setup
2088+
isInitCommand bool
20912089

2092-
// Setup the meta
2093-
m := testMetaBackend(t, nil)
2094-
m.AllowExperimentalFeatures = true
2090+
inputEnabled bool
2091+
inputText string
20952092

2096-
// Get the state store's config
2097-
mod, loadDiags := m.loadSingleModule(td)
2098-
if loadDiags.HasErrors() {
2099-
t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err())
2093+
createDefaultWorkspace bool
2094+
// assertions
2095+
expectedError string
2096+
expectDefaultWorkspaceExists bool
2097+
}{
2098+
"an init command prompts users for input when the default workspace needs to be created": {
2099+
inputEnabled: true,
2100+
createDefaultWorkspace: true,
2101+
inputText: "yes",
2102+
isInitCommand: true,
2103+
expectDefaultWorkspaceExists: true,
2104+
},
2105+
"an init command with input disabled will create the default workspace automatically": {
2106+
inputEnabled: false,
2107+
createDefaultWorkspace: true,
2108+
isInitCommand: true,
2109+
expectDefaultWorkspaceExists: true,
2110+
},
2111+
"an init command with input disabled and the flag -create-default-workspace=false will not make the default workspace": {
2112+
inputEnabled: false,
2113+
createDefaultWorkspace: false,
2114+
isInitCommand: true,
2115+
expectDefaultWorkspaceExists: false,
2116+
},
2117+
// "during a non-init command, the command ends in with an error telling the user to run an init command": {
2118+
// isInitCommand: false,
2119+
// expectedError: "State store initialization required, please run \"terraform init\": Reason: Initial configuration of the requested state_store \"foo_bar\" in provider foo (\"registry.terraform.io/my-org/foo\")",
2120+
// },
21002121
}
21012122

2102-
// Get mock provider factory to be used during init
2103-
//
2104-
// This imagines a provider called foo that contains
2105-
// a pluggable state store implementation called bar.
2106-
pssName := "test_store"
2107-
mock := testStateStoreMock(t)
2108-
factory := func() (providers.Interface, error) {
2109-
return mock, nil
2110-
}
2123+
for tn, tc := range cases {
2124+
t.Run(tn, func(t *testing.T) {
2125+
td := t.TempDir()
2126+
testCopyDir(t, testFixturePath("state-store-new"), td)
2127+
t.Chdir(td)
21112128

2112-
// Create locks - these would normally be the locks derived from config
2113-
locks := depsfile.NewLocks()
2114-
constraint, err := providerreqs.ParseVersionConstraints(">9.0.0")
2115-
if err != nil {
2116-
t.Fatalf("test setup failed when making constraint: %s", err)
2117-
}
2118-
expectedVersionString := "9.9.9"
2119-
expectedProviderSource := "registry.terraform.io/hashicorp/test"
2120-
locks.SetProvider(
2121-
addrs.MustParseProviderSourceString(expectedProviderSource),
2122-
versions.MustParseVersion(expectedVersionString),
2123-
constraint,
2124-
[]providerreqs.Hash{"h1:foo"},
2125-
)
2129+
// Setup the meta
2130+
m := testMetaBackend(t, nil)
2131+
m.AllowExperimentalFeatures = true
2132+
m.input = tc.inputEnabled
2133+
if tc.inputEnabled {
2134+
defer testInteractiveInput(t, []string{tc.inputText})()
2135+
}
21262136

2127-
// Act - get the operations backend
2128-
_, beDiags := m.Backend(&BackendOpts{
2129-
Init: true,
2130-
StateStoreConfig: mod.StateStore,
2131-
ProviderFactory: factory,
2132-
Locks: locks,
2133-
})
2134-
if beDiags.HasErrors() {
2135-
t.Fatalf("unexpected error: %s", beDiags.Err())
2136-
}
2137+
// Get the state store's config
2138+
mod, loadDiags := m.loadSingleModule(td)
2139+
if loadDiags.HasErrors() {
2140+
t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err())
2141+
}
21372142

2138-
// Check the backend state file exists & assert its contents
2139-
s := testDataStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename))
2140-
if s == nil {
2141-
t.Fatal("expected backend state file to be created, but it was missing")
2142-
}
2143+
// Get mock provider factory to be used during init
2144+
//
2145+
// This imagines a provider called foo that contains
2146+
// a pluggable state store implementation called bar.
2147+
pssName := "test_store"
2148+
mock := testStateStoreMock(t)
2149+
factory := func() (providers.Interface, error) {
2150+
return mock, nil
2151+
}
21432152

2144-
if s.StateStore.Type != pssName {
2145-
t.Fatalf("backend state file contains unexpected state store type, want %q, got %q", pssName, s.StateStore.Type)
2146-
}
2147-
if s.StateStore.Provider.Version.String() != expectedVersionString {
2148-
t.Fatalf("backend state file contains unexpected version, want %q, got %q", expectedVersionString, s.StateStore.Provider.Version)
2149-
}
2150-
if s.StateStore.Provider.Source.String() != expectedProviderSource {
2151-
t.Fatalf("backend state file contains unexpected source, want %q, got %q", expectedProviderSource, s.StateStore.Provider.Source)
2152-
}
2153-
expectedProviderConfig := "{ \"region\": \"mars\" }"
2154-
expectedStoreConfig := "{ \"value\": \"foobar\" }"
2155-
if cleanString(string(s.StateStore.Provider.ConfigRaw)) != expectedProviderConfig {
2156-
t.Fatalf("backend state file contains unexpected raw config data for the provider, want %q, got %q", expectedProviderConfig, cleanString(string(s.StateStore.Provider.ConfigRaw)))
2157-
}
2158-
if cleanString(string(s.StateStore.ConfigRaw)) != expectedStoreConfig {
2159-
t.Fatalf("backend state file contains unexpected raw config data for the state store, want %q, got %q", expectedStoreConfig, cleanString(string(s.StateStore.ConfigRaw)))
2153+
// Create locks - these would normally be the locks derived from config
2154+
locks := depsfile.NewLocks()
2155+
constraint, err := providerreqs.ParseVersionConstraints(">9.0.0")
2156+
if err != nil {
2157+
t.Fatalf("test setup failed when making constraint: %s", err)
2158+
}
2159+
expectedVersionString := "9.9.9"
2160+
expectedProviderSource := "registry.terraform.io/hashicorp/test"
2161+
locks.SetProvider(
2162+
addrs.MustParseProviderSourceString(expectedProviderSource),
2163+
versions.MustParseVersion(expectedVersionString),
2164+
constraint,
2165+
[]providerreqs.Hash{"h1:foo"},
2166+
)
2167+
2168+
// Act - get the operations backend
2169+
b, beDiags := m.Backend(&BackendOpts{
2170+
Init: tc.isInitCommand, // Changes with test case
2171+
StateStoreConfig: mod.StateStore,
2172+
ProviderFactory: factory,
2173+
Locks: locks,
2174+
CreateDefaultWorkspace: tc.createDefaultWorkspace,
2175+
})
2176+
if beDiags.HasErrors() {
2177+
if tc.expectedError == "" {
2178+
t.Fatalf("unexpected error: %s", beDiags.Err())
2179+
}
2180+
if !strings.Contains(cleanString(beDiags.Err().Error()), tc.expectedError) {
2181+
t.Fatalf("expected error to contain %s, but instead got: %s", tc.expectedError, cleanString(beDiags.Err().Error()))
2182+
}
2183+
return // error is as expected
2184+
}
2185+
if tc.expectedError != "" && !beDiags.HasErrors() {
2186+
t.Fatal("expected error missing")
2187+
}
2188+
2189+
// Check the backend state file exists & assert its contents
2190+
s := testDataStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename))
2191+
if s == nil {
2192+
t.Fatal("expected backend state file to be created, but it was missing")
2193+
}
2194+
2195+
if s.StateStore.Type != pssName {
2196+
t.Fatalf("backend state file contains unexpected state store type, want %q, got %q", pssName, s.StateStore.Type)
2197+
}
2198+
if s.StateStore.Provider.Version.String() != expectedVersionString {
2199+
t.Fatalf("backend state file contains unexpected version, want %q, got %q", expectedVersionString, s.StateStore.Provider.Version)
2200+
}
2201+
if s.StateStore.Provider.Source.String() != expectedProviderSource {
2202+
t.Fatalf("backend state file contains unexpected source, want %q, got %q", expectedProviderSource, s.StateStore.Provider.Source)
2203+
}
2204+
expectedProviderConfig := "{ \"region\": \"mars\" }"
2205+
expectedStoreConfig := "{ \"bar\": \"foobar\" }"
2206+
if cleanString(string(s.StateStore.Provider.ConfigRaw)) != expectedProviderConfig {
2207+
t.Fatalf("backend state file contains unexpected raw config data for the provider, want %q, got %q", expectedProviderConfig, cleanString(string(s.StateStore.Provider.ConfigRaw)))
2208+
}
2209+
if cleanString(string(s.StateStore.ConfigRaw)) != expectedStoreConfig {
2210+
t.Fatalf("backend state file contains unexpected raw config data for the state store, want %q, got %q", expectedStoreConfig, cleanString(string(s.StateStore.ConfigRaw)))
2211+
}
2212+
2213+
w, wDiags := b.Workspaces()
2214+
if wDiags.HasErrors() {
2215+
t.Fatalf("unexpected error: %s", wDiags.Err())
2216+
}
2217+
if len(w) == 0 {
2218+
if tc.expectDefaultWorkspaceExists {
2219+
t.Fatal("expected the default workspace to exist, but there are no workspaces")
2220+
}
2221+
return
2222+
}
2223+
if len(w) > 0 {
2224+
if tc.expectDefaultWorkspaceExists {
2225+
if len(w) == 1 && w[0] != "default" {
2226+
t.Fatalf("expected the default workspace to exist, but instead got: %v", w)
2227+
}
2228+
}
2229+
t.Fatalf("expected the default workspace to be the only existing workspace, but instead got: %v", w)
2230+
}
2231+
})
21602232
}
21612233
}
21622234

@@ -2234,27 +2306,11 @@ func TestMetaBackend_reconfigureStateStoreChange(t *testing.T) {
22342306
return mock, nil
22352307
}
22362308

2237-
// Create locks - these would normally be the locks derived from config
2238-
locks := depsfile.NewLocks()
2239-
constraint, err := providerreqs.ParseVersionConstraints(">9.0.0")
2240-
if err != nil {
2241-
t.Fatalf("test setup failed when making constraint: %s", err)
2242-
}
2243-
expectedVersionString := "9.9.9"
2244-
expectedProviderSource := "registry.terraform.io/my-org/foo"
2245-
locks.SetProvider(
2246-
addrs.MustParseProviderSourceString(expectedProviderSource),
2247-
versions.MustParseVersion(expectedVersionString),
2248-
constraint,
2249-
[]providerreqs.Hash{"h1:foo"},
2250-
)
2251-
22522309
// Get the operations backend
22532310
_, beDiags := m.Backend(&BackendOpts{
22542311
Init: true,
22552312
StateStoreConfig: mod.StateStore,
22562313
ProviderFactory: factory,
2257-
Locks: locks,
22582314
})
22592315

22602316
if !beDiags.HasErrors() {

internal/providers/testing/provider_mock.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ package testing
55

66
import (
77
"fmt"
8+
"maps"
9+
"slices"
810
"sync"
911

1012
"github.com/zclconf/go-cty/cty"
1113
ctyjson "github.com/zclconf/go-cty/cty/json"
1214
"github.com/zclconf/go-cty/cty/msgpack"
1315

16+
"github.com/hashicorp/hcl/v2"
1417
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
1518
"github.com/hashicorp/terraform/internal/providers"
1619
)
@@ -156,6 +159,9 @@ type MockProvider struct {
156159
WriteStateBytesFn func(providers.WriteStateBytesRequest) providers.WriteStateBytesResponse
157160
WriteStateBytesResponse providers.WriteStateBytesResponse
158161

162+
// states is an internal field that tracks which workspaces have been created in a test
163+
states map[string]interface{}
164+
159165
GetStatesCalled bool
160166
GetStatesResponse *providers.GetStatesResponse
161167
GetStatesRequest providers.GetStatesRequest
@@ -1032,11 +1038,8 @@ func (p *MockProvider) GetStates(r providers.GetStatesRequest) (resp providers.G
10321038
return p.GetStatesFn(r)
10331039
}
10341040

1035-
// If the mock has no further inputs, return an empty list.
1036-
// The state store should be reporting a minimum of the default workspace usually,
1037-
// but this should be achieved by querying data storage and identifying the artifact
1038-
// for that workspace, and reporting that the workspace exists.
1039-
resp.States = []string{}
1041+
// If the mock has no further inputs, return the internal states list
1042+
resp.States = slices.Sorted(maps.Keys(p.states))
10401043

10411044
return resp
10421045
}
@@ -1063,7 +1066,15 @@ func (p *MockProvider) DeleteState(r providers.DeleteStateRequest) (resp provide
10631066
return p.DeleteStateFn(r)
10641067
}
10651068

1066-
// There's no logic we can include here in the absence of other fields on the mock.
1069+
if _, match := p.states[r.StateId]; match {
1070+
delete(p.states, r.StateId)
1071+
} else {
1072+
resp.Diagnostics.Append(&hcl.Diagnostic{
1073+
Severity: hcl.DiagError,
1074+
Summary: "Workspace cannot be deleted",
1075+
Detail: fmt.Sprintf("The workspace %q does not exist, so cannot be deleted", r.StateId),
1076+
})
1077+
}
10671078

10681079
// If the response contains no diagnostics then the deletion is assumed to be successful.
10691080
return resp

0 commit comments

Comments
 (0)