mirror of https://github.com/databricks/cli.git
164 lines
4.7 KiB
Go
164 lines
4.7 KiB
Go
package terraform
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/databricks/cli/bundle"
|
|
"github.com/databricks/cli/bundle/config"
|
|
mockfiler "github.com/databricks/cli/internal/mocks/libs/filer"
|
|
"github.com/databricks/cli/libs/filer"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
func mockStateFilerForPull(t *testing.T, contents map[string]any, merr error) filer.Filer {
|
|
buf, err := json.Marshal(contents)
|
|
assert.NoError(t, err)
|
|
|
|
f := mockfiler.NewMockFiler(t)
|
|
f.
|
|
EXPECT().
|
|
Read(mock.Anything, TerraformStateFileName).
|
|
Return(io.NopCloser(bytes.NewReader(buf)), merr).
|
|
Times(1)
|
|
return f
|
|
}
|
|
|
|
func statePullTestBundle(t *testing.T) *bundle.Bundle {
|
|
return &bundle.Bundle{
|
|
BundleRootPath: t.TempDir(),
|
|
Config: config.Root{
|
|
Bundle: config.Bundle{
|
|
Target: "default",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestStatePullLocalErrorWhenRemoteHasNoLineage(t *testing.T) {
|
|
m := &statePull{}
|
|
|
|
t.Run("no local state", func(t *testing.T) {
|
|
// setup remote state.
|
|
m.filerFactory = identityFiler(mockStateFilerForPull(t, map[string]any{"serial": 5}, nil))
|
|
|
|
ctx := context.Background()
|
|
b := statePullTestBundle(t)
|
|
diags := bundle.Apply(ctx, b, m)
|
|
assert.EqualError(t, diags.Error(), "remote state file does not have a lineage")
|
|
})
|
|
|
|
t.Run("local state with lineage", func(t *testing.T) {
|
|
// setup remote state.
|
|
m.filerFactory = identityFiler(mockStateFilerForPull(t, map[string]any{"serial": 5}, nil))
|
|
|
|
ctx := context.Background()
|
|
b := statePullTestBundle(t)
|
|
writeLocalState(t, ctx, b, map[string]any{"serial": 5, "lineage": "aaaa"})
|
|
|
|
diags := bundle.Apply(ctx, b, m)
|
|
assert.EqualError(t, diags.Error(), "remote state file does not have a lineage")
|
|
})
|
|
}
|
|
|
|
func TestStatePullLocal(t *testing.T) {
|
|
tcases := []struct {
|
|
name string
|
|
|
|
// remote state before applying the pull mutators
|
|
remote map[string]any
|
|
|
|
// local state before applying the pull mutators
|
|
local map[string]any
|
|
|
|
// expected local state after applying the pull mutators
|
|
expected map[string]any
|
|
}{
|
|
{
|
|
name: "remote missing, local missing",
|
|
remote: nil,
|
|
local: nil,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "remote missing, local present",
|
|
remote: nil,
|
|
local: map[string]any{"serial": 5, "lineage": "aaaa"},
|
|
// fallback to local state, since remote state is missing.
|
|
expected: map[string]any{"serial": float64(5), "lineage": "aaaa"},
|
|
},
|
|
{
|
|
name: "local stale",
|
|
remote: map[string]any{"serial": 10, "lineage": "aaaa", "some_other_key": 123},
|
|
local: map[string]any{"serial": 5, "lineage": "aaaa"},
|
|
// use remote, since remote is newer.
|
|
expected: map[string]any{"serial": float64(10), "lineage": "aaaa", "some_other_key": float64(123)},
|
|
},
|
|
{
|
|
name: "local equal",
|
|
remote: map[string]any{"serial": 5, "lineage": "aaaa", "some_other_key": 123},
|
|
local: map[string]any{"serial": 5, "lineage": "aaaa"},
|
|
// use local state, since they are equal in terms of serial sequence.
|
|
expected: map[string]any{"serial": float64(5), "lineage": "aaaa"},
|
|
},
|
|
{
|
|
name: "local newer",
|
|
remote: map[string]any{"serial": 5, "lineage": "aaaa", "some_other_key": 123},
|
|
local: map[string]any{"serial": 6, "lineage": "aaaa"},
|
|
// use local state, since local is newer.
|
|
expected: map[string]any{"serial": float64(6), "lineage": "aaaa"},
|
|
},
|
|
{
|
|
name: "remote and local have different lineages",
|
|
remote: map[string]any{"serial": 5, "lineage": "aaaa"},
|
|
local: map[string]any{"serial": 10, "lineage": "bbbb"},
|
|
// use remote, since lineages do not match.
|
|
expected: map[string]any{"serial": float64(5), "lineage": "aaaa"},
|
|
},
|
|
{
|
|
name: "local is missing lineage",
|
|
remote: map[string]any{"serial": 5, "lineage": "aaaa"},
|
|
local: map[string]any{"serial": 10},
|
|
// use remote, since local does not have lineage.
|
|
expected: map[string]any{"serial": float64(5), "lineage": "aaaa"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
m := &statePull{}
|
|
if tc.remote == nil {
|
|
// nil represents no remote state file.
|
|
m.filerFactory = identityFiler(mockStateFilerForPull(t, nil, os.ErrNotExist))
|
|
} else {
|
|
m.filerFactory = identityFiler(mockStateFilerForPull(t, tc.remote, nil))
|
|
}
|
|
|
|
ctx := context.Background()
|
|
b := statePullTestBundle(t)
|
|
if tc.local != nil {
|
|
writeLocalState(t, ctx, b, tc.local)
|
|
}
|
|
|
|
diags := bundle.Apply(ctx, b, m)
|
|
assert.NoError(t, diags.Error())
|
|
|
|
if tc.expected == nil {
|
|
// nil represents no local state file is expected.
|
|
_, err := os.Stat(localStateFile(t, ctx, b))
|
|
assert.ErrorIs(t, err, fs.ErrNotExist)
|
|
} else {
|
|
localState := readLocalState(t, ctx, b)
|
|
assert.Equal(t, tc.expected, localState)
|
|
|
|
}
|
|
})
|
|
}
|
|
}
|