Skip to content

Commit cebf4a5

Browse files
committed
test: Add command-level test for state mv showing integration with pluggable state storage code.
1 parent 85b4a83 commit cebf4a5

File tree

1 file changed

+135
-0
lines changed

1 file changed

+135
-0
lines changed

internal/command/state_mv_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
package command
55

66
import (
7+
"bytes"
8+
"encoding/json"
79
"fmt"
810
"os"
911
"path/filepath"
@@ -14,7 +16,9 @@ import (
1416
"github.com/hashicorp/cli"
1517

1618
"github.com/hashicorp/terraform/internal/addrs"
19+
"github.com/hashicorp/terraform/internal/providers"
1720
"github.com/hashicorp/terraform/internal/states"
21+
"github.com/hashicorp/terraform/internal/states/statefile"
1822
)
1923

2024
func TestStateMv(t *testing.T) {
@@ -153,6 +157,137 @@ func TestStateMv(t *testing.T) {
153157

154158
}
155159

160+
func TestStateMv_stateStore(t *testing.T) {
161+
// Create a temporary working directory
162+
td := t.TempDir()
163+
testCopyDir(t, testFixturePath("state-list-state-store"), td)
164+
t.Chdir(td)
165+
166+
// Get bytes describing a state containing resources
167+
state := states.BuildState(func(s *states.SyncState) {
168+
s.SetResourceInstanceCurrent(
169+
addrs.Resource{
170+
Mode: addrs.ManagedResourceMode,
171+
Type: "test_instance",
172+
Name: "foo",
173+
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
174+
&states.ResourceInstanceObjectSrc{
175+
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
176+
Status: states.ObjectReady,
177+
},
178+
addrs.AbsProviderConfig{
179+
Provider: addrs.NewDefaultProvider("test"),
180+
Module: addrs.RootModule,
181+
},
182+
)
183+
s.SetResourceInstanceCurrent(
184+
addrs.Resource{
185+
Mode: addrs.ManagedResourceMode,
186+
Type: "test_instance",
187+
Name: "baz",
188+
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
189+
&states.ResourceInstanceObjectSrc{
190+
AttrsJSON: []byte(`{"id":"baz","foo":"value","bar":"value"}`),
191+
Status: states.ObjectReady,
192+
},
193+
addrs.AbsProviderConfig{
194+
Provider: addrs.NewDefaultProvider("test"),
195+
Module: addrs.RootModule,
196+
},
197+
)
198+
})
199+
var stateBuf bytes.Buffer
200+
if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil {
201+
t.Fatalf("error during test setup: %s", err)
202+
}
203+
204+
// Create a mock that contains a persisted "default" state that uses the bytes from above.
205+
mockProvider := mockPluggableStateStorageProvider(t)
206+
mockProvider.MockStates = map[string]interface{}{
207+
"default": stateBuf.Bytes(),
208+
}
209+
mockProviderAddress := addrs.NewDefaultProvider("test")
210+
providerSource, close := newMockProviderSource(t, map[string][]string{
211+
"hashicorp/test": {"1.0.0"},
212+
})
213+
defer close()
214+
215+
// Make the mock assert that the resource has been moved when the new state is persisted
216+
oldAddr := "test_instance.foo"
217+
newAddr := "test_instance.bar"
218+
mockProvider.WriteStateBytesFn = func(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse {
219+
r := bytes.NewReader(req.Bytes)
220+
file, err := statefile.Read(r)
221+
if err != nil {
222+
t.Fatal(err)
223+
}
224+
225+
root := file.State.Modules[""]
226+
if _, ok := root.Resources[oldAddr]; ok {
227+
t.Fatalf("expected the new state to have moved the %s resource to the new addr %s, but the old addr is still present",
228+
newAddr,
229+
oldAddr,
230+
)
231+
}
232+
resource, ok := root.Resources[newAddr]
233+
if !ok {
234+
t.Fatalf("expected the moved resource to be at addr %s, but it isn't present", newAddr)
235+
}
236+
237+
// Check that the moved resource has the same state.
238+
var key addrs.InstanceKey
239+
type attrsJson struct {
240+
Id string `json:"id"`
241+
Foo string `json:"foo"`
242+
Bar string `json:"bar"`
243+
}
244+
var data attrsJson
245+
attrs := resource.Instances[key].Current.AttrsJSON
246+
err = json.Unmarshal(attrs, &data)
247+
if err != nil {
248+
t.Fatal(err)
249+
}
250+
expectedData := attrsJson{
251+
Id: "foo",
252+
Foo: "value",
253+
Bar: "value",
254+
}
255+
if diff := cmp.Diff(expectedData, data); diff != "" {
256+
t.Fatalf("the state of the moved resource doesn't match the original state:\nDiff = %s", diff)
257+
}
258+
259+
return providers.WriteStateBytesResponse{}
260+
}
261+
262+
ui := new(cli.MockUi)
263+
view, _ := testView(t)
264+
c := &StateMvCommand{
265+
StateMeta{
266+
Meta: Meta{
267+
AllowExperimentalFeatures: true,
268+
testingOverrides: &testingOverrides{
269+
Providers: map[addrs.Provider]providers.Factory{
270+
mockProviderAddress: providers.FactoryFixed(mockProvider),
271+
},
272+
},
273+
ProviderSource: providerSource,
274+
Ui: ui,
275+
View: view,
276+
},
277+
},
278+
}
279+
280+
args := []string{
281+
oldAddr,
282+
newAddr,
283+
}
284+
if code := c.Run(args); code != 0 {
285+
t.Fatalf("return code: %d\n\n%s", code, ui.ErrorWriter.String())
286+
}
287+
288+
// See the mock definition above for logic that asserts what the new state will look like after moving the resource.
289+
}
290+
156291
func TestStateMv_backupAndBackupOutOptionsWithNonLocalBackend(t *testing.T) {
157292
state := states.BuildState(func(s *states.SyncState) {
158293
s.SetResourceInstanceCurrent(

0 commit comments

Comments
 (0)