backend/remote: test new options and modes

Tests for -refresh=false, -refresh-only, and -replace,
as well as tests to ensure we error appropriately on
mismatched API versions.
This commit is contained in:
CJ Horton 2021-05-13 21:28:16 -07:00
parent eb1f113693
commit ab5fec9aec
4 changed files with 382 additions and 5 deletions

View File

@ -279,6 +279,45 @@ func TestRemote_applyWithoutRefresh(t *testing.T) {
op, configCleanup, done := testOperationApply(t, "./testdata/apply") op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup() defer configCleanup()
defer done(t)
op.PlanRefresh = false
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
}
if run.PlanEmpty {
t.Fatalf("expected plan to be non-empty")
}
// We should find a run inside the mock client that has refresh set
// to false.
runsAPI := b.client.Runs.(*mockRuns)
if got, want := len(runsAPI.runs), 1; got != want {
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
}
for _, run := range runsAPI.runs {
if diff := cmp.Diff(false, run.Refresh); diff != "" {
t.Errorf("wrong Refresh setting in the created run\n%s", diff)
}
}
}
func TestRemote_applyWithoutRefreshIncompatibleAPIVersion(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
b.client.SetFakeRemoteAPIVersion("2.3")
op.PlanRefresh = false op.PlanRefresh = false
op.Workspace = backend.DefaultStateName op.Workspace = backend.DefaultStateName
@ -293,10 +332,82 @@ func TestRemote_applyWithoutRefresh(t *testing.T) {
if run.Result == backend.OperationSuccess { if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail") t.Fatal("expected apply operation to fail")
} }
if !run.PlanEmpty {
t.Fatalf("expected plan to be empty")
}
errOutput := output.Stderr() errOutput := output.Stderr()
if !strings.Contains(errOutput, "refresh is currently not supported") { if !strings.Contains(errOutput, "Planning without refresh is not supported") {
t.Fatalf("expected a refresh error, got: %v", errOutput) t.Fatalf("expected a not supported error, got: %v", errOutput)
}
}
func TestRemote_applyWithRefreshOnly(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
op.PlanMode = plans.RefreshOnlyMode
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
}
if run.PlanEmpty {
t.Fatalf("expected plan to be non-empty")
}
// We should find a run inside the mock client that has refresh-only set
// to true.
runsAPI := b.client.Runs.(*mockRuns)
if got, want := len(runsAPI.runs), 1; got != want {
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
}
for _, run := range runsAPI.runs {
if diff := cmp.Diff(true, run.RefreshOnly); diff != "" {
t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff)
}
}
}
func TestRemote_applyWithRefreshOnlyIncompatibleAPIVersion(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
b.client.SetFakeRemoteAPIVersion("2.3")
op.PlanMode = plans.RefreshOnlyMode
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
if !run.PlanEmpty {
t.Fatalf("expected plan to be empty")
}
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Refresh-only mode is not supported") {
t.Fatalf("expected a not supported error, got: %v", errOutput)
} }
} }
@ -375,6 +486,79 @@ func TestRemote_applyWithTargetIncompatibleAPIVersion(t *testing.T) {
} }
} }
func TestRemote_applyWithReplace(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
op.ForceReplace = []addrs.AbsResourceInstance{addr}
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatal("expected plan operation to succeed")
}
if run.PlanEmpty {
t.Fatalf("expected plan to be non-empty")
}
// We should find a run inside the mock client that has the same
// refresh address we requested above.
runsAPI := b.client.Runs.(*mockRuns)
if got, want := len(runsAPI.runs), 1; got != want {
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
}
for _, run := range runsAPI.runs {
if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" {
t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff)
}
}
}
func TestRemote_applyWithReplaceIncompatibleAPIVersion(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
b.client.SetFakeRemoteAPIVersion("2.3")
addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
op.ForceReplace = []addrs.AbsResourceInstance{addr}
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
if !run.PlanEmpty {
t.Fatalf("expected plan to be empty")
}
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Planning resource replacements is not supported") {
t.Fatalf("expected a not supported error, got: %v", errOutput)
}
}
func TestRemote_applyWithVariables(t *testing.T) { func TestRemote_applyWithVariables(t *testing.T) {
b, bCleanup := testBackendDefault(t) b, bCleanup := testBackendDefault(t)
defer bCleanup() defer bCleanup()

View File

@ -788,6 +788,7 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
HasChanges: false, HasChanges: false,
Permissions: &tfe.RunPermissions{}, Permissions: &tfe.RunPermissions{},
Plan: p, Plan: p,
ReplaceAddrs: options.ReplaceAddrs,
Status: tfe.RunPending, Status: tfe.RunPending,
TargetAddrs: options.TargetAddrs, TargetAddrs: options.TargetAddrs,
} }
@ -804,6 +805,14 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
r.IsDestroy = *options.IsDestroy r.IsDestroy = *options.IsDestroy
} }
if options.Refresh != nil {
r.Refresh = *options.Refresh
}
if options.RefreshOnly != nil {
r.RefreshOnly = *options.RefreshOnly
}
w, ok := m.client.Workspaces.workspaceIDs[options.Workspace.ID] w, ok := m.client.Workspaces.workspaceIDs[options.Workspace.ID]
if !ok { if !ok {
return nil, tfe.ErrResourceNotFound return nil, tfe.ErrResourceNotFound

View File

@ -284,6 +284,45 @@ func TestRemote_planWithoutRefresh(t *testing.T) {
op, configCleanup, done := testOperationPlan(t, "./testdata/plan") op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup() defer configCleanup()
defer done(t)
op.PlanRefresh = false
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
}
if run.PlanEmpty {
t.Fatal("expected a non-empty plan")
}
// We should find a run inside the mock client that has refresh set
// to false.
runsAPI := b.client.Runs.(*mockRuns)
if got, want := len(runsAPI.runs), 1; got != want {
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
}
for _, run := range runsAPI.runs {
if diff := cmp.Diff(false, run.Refresh); diff != "" {
t.Errorf("wrong Refresh setting in the created run\n%s", diff)
}
}
}
func TestRemote_planWithoutRefreshIncompatibleAPIVersion(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
b.client.SetFakeRemoteAPIVersion("2.3")
op.PlanRefresh = false op.PlanRefresh = false
op.Workspace = backend.DefaultStateName op.Workspace = backend.DefaultStateName
@ -298,10 +337,82 @@ func TestRemote_planWithoutRefresh(t *testing.T) {
if run.Result == backend.OperationSuccess { if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail") t.Fatal("expected plan operation to fail")
} }
if !run.PlanEmpty {
t.Fatalf("expected plan to be empty")
}
errOutput := output.Stderr() errOutput := output.Stderr()
if !strings.Contains(errOutput, "refresh is currently not supported") { if !strings.Contains(errOutput, "Planning without refresh is not supported") {
t.Fatalf("expected a refresh error, got: %v", errOutput) t.Fatalf("expected not supported error, got: %v", errOutput)
}
}
func TestRemote_planWithRefreshOnly(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
op.PlanMode = plans.RefreshOnlyMode
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
}
if run.PlanEmpty {
t.Fatal("expected a non-empty plan")
}
// We should find a run inside the mock client that has refresh-only set
// to true.
runsAPI := b.client.Runs.(*mockRuns)
if got, want := len(runsAPI.runs), 1; got != want {
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
}
for _, run := range runsAPI.runs {
if diff := cmp.Diff(true, run.RefreshOnly); diff != "" {
t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff)
}
}
}
func TestRemote_planWithRefreshOnlyIncompatibleAPIVersion(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
b.client.SetFakeRemoteAPIVersion("2.3")
op.PlanMode = plans.RefreshOnlyMode
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
if !run.PlanEmpty {
t.Fatalf("expected plan to be empty")
}
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Refresh-only mode is not supported") {
t.Fatalf("expected not supported error, got: %v", errOutput)
} }
} }
@ -414,6 +525,79 @@ func TestRemote_planWithTargetIncompatibleAPIVersion(t *testing.T) {
} }
} }
func TestRemote_planWithReplace(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
op.ForceReplace = []addrs.AbsResourceInstance{addr}
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatal("expected plan operation to succeed")
}
if run.PlanEmpty {
t.Fatalf("expected plan to be non-empty")
}
// We should find a run inside the mock client that has the same
// refresh address we requested above.
runsAPI := b.client.Runs.(*mockRuns)
if got, want := len(runsAPI.runs), 1; got != want {
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
}
for _, run := range runsAPI.runs {
if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" {
t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff)
}
}
}
func TestRemote_planWithReplaceIncompatibleAPIVersion(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
b.client.SetFakeRemoteAPIVersion("2.3")
addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
op.ForceReplace = []addrs.AbsResourceInstance{addr}
op.Workspace = backend.DefaultStateName
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
if !run.PlanEmpty {
t.Fatalf("expected plan to be empty")
}
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Planning resource replacements is not supported") {
t.Fatalf("expected not supported error, got: %v", errOutput)
}
}
func TestRemote_planWithVariables(t *testing.T) { func TestRemote_planWithVariables(t *testing.T) {
b, bCleanup := testBackendDefault(t) b, bCleanup := testBackendDefault(t)
defer bCleanup() defer bCleanup()

View File

@ -200,7 +200,7 @@ func testServer(t *testing.T) *httptest.Server {
// Respond to pings to get the API version header. // Respond to pings to get the API version header.
mux.HandleFunc("/api/v2/ping", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/api/v2/ping", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Header().Set("TFP-API-Version", "2.3") w.Header().Set("TFP-API-Version", "2.4")
}) })
// Respond to the initial query to read the hashicorp org entitlements. // Respond to the initial query to read the hashicorp org entitlements.