// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package command

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/hashicorp/cli"
	version "github.com/hashicorp/go-version"
	tfaddr "github.com/hashicorp/terraform-registry-address"
	"github.com/zclconf/go-cty/cty"

	"github.com/hashicorp/terraform/internal/addrs"
	"github.com/hashicorp/terraform/internal/collections"
	"github.com/hashicorp/terraform/internal/command/clistate"
	"github.com/hashicorp/terraform/internal/configs/configschema"
	"github.com/hashicorp/terraform/internal/getproviders"
	"github.com/hashicorp/terraform/internal/plans"
	"github.com/hashicorp/terraform/internal/providers"
	testing_provider "github.com/hashicorp/terraform/internal/providers/testing"
	"github.com/hashicorp/terraform/internal/states"
	"github.com/hashicorp/terraform/internal/states/statemgr"
	"github.com/hashicorp/terraform/internal/terminal"
	"github.com/hashicorp/terraform/internal/tfdiags"
)

func TestApply(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := applyFixtureProvider()

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
}

func TestApply_path(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	p := applyFixtureProvider()

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-auto-approve",
		testFixturePath("apply"),
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}
	if !strings.Contains(output.Stderr(), "-chdir") {
		t.Fatal("expected command output to refer to -chdir flag, but got:", output.Stderr())
	}
}

func TestApply_approveNo(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	_ = testInputMap(t, map[string]string{
		"approve": "no",
	})

	// Do not use the NewMockUi initializer here, as we want to delay
	// the call to init until after setting up the input mocks
	ui := new(cli.MockUi)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			Ui:               ui,
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}
	if got, want := output.Stdout(), "Apply cancelled"; !strings.Contains(got, want) {
		t.Fatalf("expected output to include %q, but was:\n%s", want, got)
	}

	if _, err := os.Stat(statePath); err == nil || !os.IsNotExist(err) {
		t.Fatalf("state file should not exist")
	}
}

func TestApply_approveYes(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := applyFixtureProvider()

	_ = testInputMap(t, map[string]string{
		"approve": "yes",
	})

	// Do not use the NewMockUi initializer here, as we want to delay
	// the call to init until after setting up the input mocks
	ui := new(cli.MockUi)

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			Ui:               ui,
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
}

// test apply with locked state
func TestApply_lockedState(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	unlock, err := testLockState(t, testDataDir, statePath)
	if err != nil {
		t.Fatal(err)
	}
	defer unlock()

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code == 0 {
		t.Fatal("expected error")
	}

	if !strings.Contains(output.Stderr(), "lock") {
		t.Fatal("command output does not look like a lock error:", output.Stderr())
	}
}

// test apply with locked state, waiting for unlock
func TestApply_lockedStateWait(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	unlock, err := testLockState(t, testDataDir, statePath)
	if err != nil {
		t.Fatal(err)
	}

	// unlock during apply
	go func() {
		time.Sleep(500 * time.Millisecond)
		unlock()
	}()

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	// wait 4s just in case the lock process doesn't release in under a second,
	// and we want our context to be alive for a second retry at the 3s mark.
	args := []string{
		"-state", statePath,
		"-lock-timeout", "4s",
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("lock should have succeeded in less than 3s: %s", output.Stderr())
	}
}

// Verify that the parallelism flag allows no more than the desired number of
// concurrent calls to ApplyResourceChange.
func TestApply_parallelism(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("parallelism"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	par := 4

	// started is a semaphore that we use to ensure that we never have more
	// than "par" apply operations happening concurrently
	started := make(chan struct{}, par)

	// beginCtx is used as a starting gate to hold back ApplyResourceChange
	// calls until we reach the desired concurrency. The cancel func "begin" is
	// called once we reach the desired concurrency, allowing all apply calls
	// to proceed in unison.
	beginCtx, begin := context.WithCancel(context.Background())

	// This just makes go vet happy, in reality the function will never exit if
	// begin() isn't called inside ApplyResourceChangeFn.
	defer begin()

	// Since our mock provider has its own mutex preventing concurrent calls
	// to ApplyResourceChange, we need to use a number of separate providers
	// here. They will all have the same mock implementation function assigned
	// but crucially they will each have their own mutex.
	providerFactories := map[addrs.Provider]providers.Factory{}
	for i := 0; i < 10; i++ {
		name := fmt.Sprintf("test%d", i)
		provider := &testing_provider.MockProvider{}
		provider.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
			ResourceTypes: map[string]providers.Schema{
				name + "_instance": {Body: &configschema.Block{}},
			},
		}
		provider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
			return providers.PlanResourceChangeResponse{
				PlannedState: req.ProposedNewState,
			}
		}
		provider.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
			// If we ever have more than our intended parallelism number of
			// apply operations running concurrently, the semaphore will fail.
			select {
			case started <- struct{}{}:
				defer func() {
					<-started
				}()
			default:
				t.Fatal("too many concurrent apply operations")
			}

			// If we never reach our intended parallelism, the context will
			// never be canceled and the test will time out.
			if len(started) >= par {
				begin()
			}
			<-beginCtx.Done()

			// do some "work"
			// Not required for correctness, but makes it easier to spot a
			// failure when there is more overlap.
			time.Sleep(10 * time.Millisecond)

			return providers.ApplyResourceChangeResponse{
				NewState: cty.EmptyObjectVal,
			}
		}
		providerFactories[addrs.NewDefaultProvider(name)] = providers.FactoryFixed(provider)
	}
	testingOverrides := &testingOverrides{
		Providers: providerFactories,
	}

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: testingOverrides,
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
		fmt.Sprintf("-parallelism=%d", par),
	}

	res := c.Run(args)
	output := done(t)
	if res != 0 {
		t.Fatal(output.Stdout())
	}
}

func TestApply_configInvalid(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-config-invalid"), td)
	t.Chdir(td)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", testTempFile(t),
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("bad: \n%s", output.Stdout())
	}
}

func TestApply_defaultState(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := filepath.Join(td, DefaultStateFilename)

	// Change to the temporary directory
	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if err := os.Chdir(filepath.Dir(statePath)); err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.Chdir(cwd)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	// create an existing state file
	localState := statemgr.NewFilesystem(statePath)
	if err := localState.WriteState(states.NewState()); err != nil {
		t.Fatal(err)
	}

	args := []string{
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
}

func TestApply_error(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-error"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	var lock sync.Mutex
	errored := false
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
		lock.Lock()
		defer lock.Unlock()

		if !errored {
			errored = true
			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error"))
		}

		s := req.PlannedState.AsValueMap()
		s["id"] = cty.StringVal("foo")

		resp.NewState = cty.ObjectVal(s)
		return
	}
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
		s := req.ProposedNewState.AsValueMap()
		s["id"] = cty.UnknownVal(cty.String)
		resp.PlannedState = cty.ObjectVal(s)
		return
	}
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id":    {Type: cty.String, Optional: true, Computed: true},
						"ami":   {Type: cty.String, Optional: true},
						"error": {Type: cty.Bool, Optional: true},
					},
				},
			},
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("wrong exit code %d; want 1\n%s", code, output.Stdout())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
	if len(state.RootModule().Resources) == 0 {
		t.Fatal("no resources in state")
	}
}

func TestApply_input(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-input"), td)
	t.Chdir(td)

	// Disable test mode so input would be asked
	test = false
	defer func() { test = true }()

	// The configuration for this test includes a declaration of variable
	// "foo" with no default, and we don't set it on the command line below,
	// so the apply command will produce an interactive prompt for the
	// value of var.foo. We'll answer "foo" here, and we expect the output
	// value "result" to echo that back to us below.
	defaultInputReader = bytes.NewBufferString("foo\n")
	defaultInputWriter = new(bytes.Buffer)

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	expected := strings.TrimSpace(`
<no state>
Outputs:

result = foo
	`)
	testStateOutput(t, statePath, expected)
}

// When only a partial set of the variables are set, Terraform
// should still ask for the unset ones by default (with -input=true)
func TestApply_inputPartial(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-input-partial"), td)
	t.Chdir(td)

	// Disable test mode so input would be asked
	test = false
	defer func() { test = true }()

	// Set some default reader/writers for the inputs
	defaultInputReader = bytes.NewBufferString("one\ntwo\n")
	defaultInputWriter = new(bytes.Buffer)

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
		"-var", "foo=foovalue",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	expected := strings.TrimSpace(`
<no state>
Outputs:

bar = one
foo = foovalue
	`)
	testStateOutput(t, statePath, expected)
}

func TestApply_noArgs(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
}

func TestApply_plan(t *testing.T) {
	// Disable test mode so input would be asked
	test = false
	defer func() { test = true }()

	// Set some default reader/writers for the inputs
	defaultInputReader = new(bytes.Buffer)
	defaultInputWriter = new(bytes.Buffer)

	planPath := applyFixturePlanFile(t)
	statePath := testTempFile(t)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state-out", statePath,
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
}

// Test the ability to apply a plan file with a state store.
//
// The state store's details (provider, config, etc) are supplied by the plan,
// which allows this test to not use any configuration.
func TestApply_plan_stateStore(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	t.Chdir(td)

	// Create the plan file that includes a state store
	ver := version.Must(version.NewVersion("1.2.3"))
	providerCfg := cty.ObjectVal(map[string]cty.Value{
		"region": cty.StringVal("spain"),
	})
	providerCfgRaw, err := plans.NewDynamicValue(providerCfg, providerCfg.Type())
	if err != nil {
		t.Fatal(err)
	}
	storeCfg := cty.ObjectVal(map[string]cty.Value{
		"value": cty.StringVal("foobar"),
	})
	storeCfgRaw, err := plans.NewDynamicValue(storeCfg, storeCfg.Type())
	if err != nil {
		t.Fatal(err)
	}

	plan := &plans.Plan{
		Changes: plans.NewChangesSrc(),

		// We'll default to the fake plan being both applyable and complete,
		// since that's what most tests expect. Tests can override these
		// back to false again afterwards if they need to.
		Applyable: true,
		Complete:  true,

		StateStore: &plans.StateStore{
			Type: "test_store",
			Provider: &plans.Provider{
				Version: ver,
				Source: &tfaddr.Provider{
					Hostname:  tfaddr.DefaultProviderRegistryHost,
					Namespace: "hashicorp",
					Type:      "test",
				},
				Config: providerCfgRaw,
			},
			Config:    storeCfgRaw,
			Workspace: "default",
		},
	}

	// Create a plan file on disk
	//
	// In this process we create a plan file describing the creation of a test_instance.foo resource.
	state := testState() // State describes
	_, snap := testModuleWithSnapshot(t, "apply")
	planPath := testPlanFile(t, snap, state, plan)

	// Create a mock, to be used as the pluggable state store described in the planfile
	mock := testStateStoreMockWithChunkNegotiation(t, 1000)
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: &testingOverrides{
				Providers: map[addrs.Provider]providers.Factory{
					addrs.NewDefaultProvider("test"): providers.FactoryFixed(mock),
				},
			},
			View: view,
		},
	}

	args := []string{
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if !mock.WriteStateBytesCalled {
		t.Fatal("expected the test to write new state when applying the plan, but WriteStateBytesCalled is false on the mock provider.")
	}
}

func TestApply_plan_stateStore_providerSupplyModes(t *testing.T) {
	t.Run("can run init, plan, apply workflow using a dev override provider for PSS", func(t *testing.T) {
		// Create a temporary, uninitialized working directory with configuration including a state store
		td := t.TempDir()
		t.Chdir(td)

		cfg := `terraform {

  required_providers {
    test = {
      source = "hashicorp/test"
    }
  }
  state_store "test_store" {
    provider "test" {
    }

    value = "foobar"
  }
}

output "foobar" {
  value = "ok"
}
`
		if err := os.WriteFile("main.tf", []byte(cfg), 0644); err != nil {
			t.Fatalf("err: %s", err)
		}

		// Mock provider still needs to be supplied via testingOverrides despite the mock HTTP source
		mockProvider := mockPluggableStateStorageProvider()
		mockProviderAddress := addrs.NewDefaultProvider("test")
		source := newMockProviderSource(t, map[string][]string{
			// The test fixture config has no version constraints, so the latest version will
			// be used; below is the 'latest' version in the test world.
			"hashicorp/test": {"1.2.3"},
		})

		ui := new(cli.MockUi)
		view, done := testView(t)
		meta := Meta{
			Ui:                        ui,
			View:                      view,
			AllowExperimentalFeatures: true,
			testingOverrides: &testingOverrides{
				Providers: map[addrs.Provider]providers.Factory{
					mockProviderAddress: providers.FactoryFixed(mockProvider),
				},
			},
			ProviderSource: source,

			// THIS ALLOWS THE TEST TO MIMIC A SCENARIO WHERE THE PROVIDER IS A DEV OVERRIDE.
			// The mock is still accessed via testingOverrides, but Terraform believes that it's a dev override due to
			// its presence in the ProviderDevOverrides map.
			ProviderDevOverrides: map[addrs.Provider]getproviders.PackageLocalDir{
				mockProviderAddress: ".",
			},
		}

		// Init
		initCmd := &InitCommand{
			Meta: meta,
		}
		args := []string{"-enable-pluggable-state-storage-experiment=true"}
		if code := initCmd.Run(args); code != 0 {
			t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, done(t).All())
		}

		// Assert backend state file says the provider is a dev override
		statePath := filepath.Join(meta.DataDir(), DefaultStateFilename)
		sMgr := &clistate.LocalState{Path: statePath}
		if err := sMgr.RefreshState(); err != nil {
			t.Fatal("Failed to load state:", err)
		}
		s := sMgr.State()
		if s == nil || s.StateStore == nil {
			t.Fatal("expected backend state file to be created and include state store details, but it was missing.")
		}
		if s.StateStore.ProviderSupplyMode != getproviders.DevOverride {
			t.Fatalf("expected state store provider supply mode to be 'dev_override', got '%s'", s.StateStore.ProviderSupplyMode)
		}

		// Plan - to produce a plan file.
		//
		// The plan file will not include version data, as the provider is considered to be a dev override.
		ui = new(cli.MockUi)
		meta.Ui = ui
		view, done = testView(t)
		meta.View = view
		planCmd := &PlanCommand{
			Meta: meta,
		}
		args = []string{
			"-out=planfile",
		}
		if code := planCmd.Run(args); code != 0 {
			t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, done(t).All())
		}

		// Apply - to use the plan file that lacks version data.
		ui = new(cli.MockUi)
		meta.Ui = ui
		view, done = testView(t)
		meta.View = view
		applyCmd := &ApplyCommand{
			Meta: meta,
		}
		args = []string{
			"planfile",
		}
		if code := applyCmd.Run(args); code != 0 {
			t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, done(t).All())
		}
	})
	t.Run("can run init, plan, apply workflow using a builtin provider for PSS", func(t *testing.T) {
		// Create a temporary, uninitialized working directory with configuration including a state store
		td := t.TempDir()
		t.Chdir(td)

		cfg := `terraform {

  required_providers {
    terraform = {
      source = "terraform.io/builtin/terraform"
    }
  }
  state_store "terraform_store" {
    provider "terraform" {
    }

    value = "foobar"
  }
}

output "foobar" {
  value = "ok"
}
`
		if err := os.WriteFile("main.tf", []byte(cfg), 0644); err != nil {
			t.Fatalf("err: %s", err)
		}

		// THIS ALLOWS THE TEST TO MIMIC A SCENARIO WHERE THE PSS PROVIDER IS BUILTIN.
		// Terraform validates that builtin providers are called 'terraform' but we can still supply
		// a mock in place of the actual builtin terraform provider. We just need the address and any
		// schemas to match the builtin provider.
		mockProvider := mockPluggableStateStorageProvider()
		schema := mockProvider.GetProviderSchema()
		schema.StateStores["terraform_store"] = schema.StateStores["test_store"] // rename to match pretending this is a store in the builtin terraform provider.
		mockProvider.GetProviderSchemaResponse = &schema
		mockProviderAddress := addrs.NewBuiltInProvider("terraform")

		source := newMockProviderSource(t, map[string][]string{
			"hashicorp/terraform": {"1.2.3"},
		})

		ui := new(cli.MockUi)
		view, done := testView(t)
		meta := Meta{
			Ui:                        ui,
			View:                      view,
			AllowExperimentalFeatures: true,
			testingOverrides: &testingOverrides{
				Providers: map[addrs.Provider]providers.Factory{
					mockProviderAddress: providers.FactoryFixed(mockProvider),
				},
			},
			ProviderSource: source,
		}

		// Init
		initCmd := &InitCommand{
			Meta: meta,
		}
		args := []string{"-enable-pluggable-state-storage-experiment=true"}
		if code := initCmd.Run(args); code != 0 {
			t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, done(t).All())
		}

		// Assert backend state file says the provider is built in
		statePath := filepath.Join(meta.DataDir(), DefaultStateFilename)
		sMgr := &clistate.LocalState{Path: statePath}
		if err := sMgr.RefreshState(); err != nil {
			t.Fatal("Failed to load state:", err)
		}
		s := sMgr.State()
		if s == nil || s.StateStore == nil {
			t.Fatal("expected backend state file to be created and include state store details, but it was missing.")
		}
		if s.StateStore.ProviderSupplyMode != getproviders.BuiltIn {
			t.Fatalf("expected state store provider supply mode to be 'built_in', got '%s'", s.StateStore.ProviderSupplyMode)
		}

		// Plan - to produce a plan file.
		//
		// The plan file will not include version data, as the provider is considered to be a dev override.
		ui = new(cli.MockUi)
		meta.Ui = ui
		view, done = testView(t)
		meta.View = view
		planCmd := &PlanCommand{
			Meta: meta,
		}
		args = []string{
			"-out=planfile",
		}
		if code := planCmd.Run(args); code != 0 {
			t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, done(t).All())
		}

		// Apply - to use the plan file that lacks version data.
		ui = new(cli.MockUi)
		meta.Ui = ui
		view, done = testView(t)
		meta.View = view
		applyCmd := &ApplyCommand{
			Meta: meta,
		}
		args = []string{
			"planfile",
		}
		if code := applyCmd.Run(args); code != 0 {
			t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, done(t).All())
		}
	})

	// See E2E tests for testing swapping between unmanaged(reattached and dev_override) and managed providers across the workflow.
}

// Test unhappy paths when applying a plan file describing a state store.
func TestApply_plan_stateStore_errorCases(t *testing.T) {
	// Disable test mode so input would be asked
	test = false
	defer func() { test = true }()

	t.Run("error when the provider doesn't include the state store named in the plan", func(t *testing.T) {
		// Set some default reader/writers for the inputs
		defaultInputReader = new(bytes.Buffer)
		defaultInputWriter = new(bytes.Buffer)

		// Create the plan file that includes a state store
		ver := version.Must(version.NewVersion("1.2.3"))
		providerCfg := cty.ObjectVal(map[string]cty.Value{
			"region": cty.StringVal("spain"),
		})
		providerCfgRaw, err := plans.NewDynamicValue(providerCfg, providerCfg.Type())
		if err != nil {
			t.Fatal(err)
		}
		storeCfg := cty.ObjectVal(map[string]cty.Value{
			"value": cty.StringVal("foobar"),
		})
		storeCfgRaw, err := plans.NewDynamicValue(storeCfg, storeCfg.Type())
		if err != nil {
			t.Fatal(err)
		}

		plan := &plans.Plan{
			Changes: plans.NewChangesSrc(),

			// We'll default to the fake plan being both applyable and complete,
			// since that's what most tests expect. Tests can override these
			// back to false again afterwards if they need to.
			Applyable: true,
			Complete:  true,

			StateStore: &plans.StateStore{
				Type: "test_doesnt_exist", // Mismatched with test_store in the provider
				Provider: &plans.Provider{
					Version: ver,
					Source: &tfaddr.Provider{
						Hostname:  tfaddr.DefaultProviderRegistryHost,
						Namespace: "hashicorp",
						Type:      "test",
					},
					Config: providerCfgRaw,
				},
				Config:    storeCfgRaw,
				Workspace: "default",
			},
		}

		// Create a plan file on disk
		//
		// In this process we create a plan file describing the creation of a test_instance.foo resource.
		state := testState() // State describes
		_, snap := testModuleWithSnapshot(t, "apply")
		planPath := testPlanFile(t, snap, state, plan)

		// Create a mock, to be used as the pluggable state store described in the planfile
		mock := testStateStoreMockWithChunkNegotiation(t, 1000)
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: &testingOverrides{
					Providers: map[addrs.Provider]providers.Factory{
						addrs.NewDefaultProvider("test"): providers.FactoryFixed(mock),
					},
				},
				View: view,
			},
		}

		args := []string{
			planPath,
			"-no-color",
		}
		code := c.Run(args)
		output := done(t)
		if code != 1 {
			t.Fatalf("expected an error but got none: %d\n\n%s", code, output.Stdout())
		}
		expectedErr := "Error: State store not implemented by the provider"
		if !strings.Contains(output.Stderr(), expectedErr) {
			t.Fatalf("expected error to include %q, but got:\n%s", expectedErr, output.Stderr())
		}
	})

	t.Run("error when the provider doesn't implement state stores", func(t *testing.T) {
		// Set some default reader/writers for the inputs
		defaultInputReader = new(bytes.Buffer)
		defaultInputWriter = new(bytes.Buffer)

		// Create the plan file that includes a state store
		ver := version.Must(version.NewVersion("1.2.3"))
		providerCfg := cty.ObjectVal(map[string]cty.Value{
			"region": cty.StringVal("spain"),
		})
		providerCfgRaw, err := plans.NewDynamicValue(providerCfg, providerCfg.Type())
		if err != nil {
			t.Fatal(err)
		}
		storeCfg := cty.ObjectVal(map[string]cty.Value{
			"value": cty.StringVal("foobar"),
		})
		storeCfgRaw, err := plans.NewDynamicValue(storeCfg, storeCfg.Type())
		if err != nil {
			t.Fatal(err)
		}

		plan := &plans.Plan{
			Changes: plans.NewChangesSrc(),

			// We'll default to the fake plan being both applyable and complete,
			// since that's what most tests expect. Tests can override these
			// back to false again afterwards if they need to.
			Applyable: true,
			Complete:  true,

			StateStore: &plans.StateStore{
				Type: "test_store",
				Provider: &plans.Provider{
					Version: ver,
					Source: &tfaddr.Provider{
						Hostname:  tfaddr.DefaultProviderRegistryHost,
						Namespace: "hashicorp",
						Type:      "test",
					},
					Config: providerCfgRaw,
				},
				Config:    storeCfgRaw,
				Workspace: "default",
			},
		}

		// Create a plan file on disk
		//
		// In this process we create a plan file describing the creation of a test_instance.foo resource.
		state := testState() // State describes
		_, snap := testModuleWithSnapshot(t, "apply")
		planPath := testPlanFile(t, snap, state, plan)

		// Create a mock, to be used as the pluggable state store described in the planfile
		mock := &testing_provider.MockProvider{}
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: &testingOverrides{
					Providers: map[addrs.Provider]providers.Factory{
						addrs.NewDefaultProvider("test"): providers.FactoryFixed(mock),
					},
				},
				View: view,
			},
		}

		args := []string{
			planPath,
			"-no-color",
		}
		code := c.Run(args)
		output := done(t)
		if code != 1 {
			t.Fatalf("expected an error but got none: %d\n\n%s", code, output.Stdout())
		}
		expectedErr := "Error: Provider does not support pluggable state storage"
		if !strings.Contains(output.Stderr(), expectedErr) {
			t.Fatalf("expected error to include %q, but got:\n%s", expectedErr, output.Stderr())
		}
	})
}

func TestApply_plan_backup(t *testing.T) {
	statePath := testTempFile(t)
	backupPath := testTempFile(t)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	// create a state file that needs to be backed up
	fs := statemgr.NewFilesystem(statePath)
	fs.StateSnapshotMeta()
	err := fs.WriteState(states.NewState())
	if err != nil {
		t.Fatal(err)
	}

	// the plan file must contain the metadata from the prior state to be
	// backed up
	planPath := applyFixturePlanFileMatchState(t, fs.StateSnapshotMeta())

	args := []string{
		"-state", statePath,
		"-backup", backupPath,
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Should have a backup file
	testStateRead(t, backupPath)
}

func TestApply_plan_noBackup(t *testing.T) {
	planPath := applyFixturePlanFile(t)
	statePath := testTempFile(t)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state-out", statePath,
		"-backup", "-",
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Ensure there is no backup
	_, err := os.Stat(statePath + DefaultBackupExtension)
	if err == nil || !os.IsNotExist(err) {
		t.Fatalf("backup should not exist")
	}

	// Ensure there is no literal "-"
	_, err = os.Stat("-")
	if err == nil || !os.IsNotExist(err) {
		t.Fatalf("backup should not exist")
	}
}

func TestApply_plan_remoteState(t *testing.T) {
	// Disable test mode so input would be asked
	test = false
	defer func() { test = true }()
	tmp := t.TempDir()
	t.Chdir(tmp)
	remoteStatePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename)
	if err := os.MkdirAll(filepath.Dir(remoteStatePath), 0755); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Set some default reader/writers for the inputs
	defaultInputReader = new(bytes.Buffer)
	defaultInputWriter = new(bytes.Buffer)

	// Create a remote state
	state := testState()
	_, srv := testRemoteState(t, state, 200)
	defer srv.Close()

	_, snap := testModuleWithSnapshot(t, "apply")
	backendConfig := cty.ObjectVal(map[string]cty.Value{
		"address":                   cty.StringVal(srv.URL),
		"update_method":             cty.NullVal(cty.String),
		"lock_address":              cty.NullVal(cty.String),
		"unlock_address":            cty.NullVal(cty.String),
		"lock_method":               cty.NullVal(cty.String),
		"unlock_method":             cty.NullVal(cty.String),
		"username":                  cty.NullVal(cty.String),
		"password":                  cty.NullVal(cty.String),
		"skip_cert_verification":    cty.NullVal(cty.Bool),
		"retry_max":                 cty.NullVal(cty.String),
		"retry_wait_min":            cty.NullVal(cty.String),
		"retry_wait_max":            cty.NullVal(cty.String),
		"client_ca_certificate_pem": cty.NullVal(cty.String),
		"client_certificate_pem":    cty.NullVal(cty.String),
		"client_private_key_pem":    cty.NullVal(cty.String),
	})
	backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type())
	if err != nil {
		t.Fatal(err)
	}
	planPath := testPlanFile(t, snap, state, &plans.Plan{
		Backend: &plans.Backend{
			Type:      "http",
			Config:    backendConfigRaw,
			Workspace: "default",
		},
		Changes: plans.NewChangesSrc(),
	})

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// State file should be not be installed
	if _, err := os.Stat(filepath.Join(tmp, DefaultStateFilename)); err == nil {
		data, _ := ioutil.ReadFile(DefaultStateFilename)
		t.Fatalf("State path should not exist: %s", string(data))
	}

	// Check that there is no remote state config
	if src, err := ioutil.ReadFile(remoteStatePath); err == nil {
		t.Fatalf("has %s file; should not\n%s", remoteStatePath, src)
	}
}

func TestApply_planWithVars(t *testing.T) {
	// Regression test for apply with a plan file that includes variables,
	// to ensure that apply doesn't prompt for the variables
	tDir := testTempDir(t)

	// The value of foo is the same as in the var file
	planPath := applyFixturePlanFileWithVariableValue(t, "bar")
	statePath := testTempFile(t)

	t.Chdir(tDir)

	p := applyFixtureProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id":    {Type: cty.String, Computed: true},
						"value": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state-out", statePath,
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
}

func TestApply_planWithVarFile(t *testing.T) {
	varFileDir := testTempDir(t)
	varFilePath := filepath.Join(varFileDir, "terraform.tfvars")
	if err := os.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The value of foo is the same as in the var file
	planPath := applyFixturePlanFileWithVariableValue(t, "bar")
	statePath := testTempFile(t)

	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if err := os.Chdir(varFileDir); err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.Chdir(cwd)

	p := applyFixtureProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id":    {Type: cty.String, Computed: true},
						"value": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state-out", statePath,
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
}

func TestApply_planWithVarFileChangingVariableValue(t *testing.T) {
	varFileDir := testTempDir(t)
	varFilePath := filepath.Join(varFileDir, "terraform-test.tfvars")
	if err := os.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The value of foo is different from the var file
	planPath := applyFixturePlanFileWithVariableValue(t, "lorem ipsum")
	statePath := testTempFile(t)

	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if err := os.Chdir(varFileDir); err != nil {
		t.Fatalf("err: %s", err)
	}
	defer os.Chdir(cwd)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state-out", statePath,
		"-var-file", varFilePath,
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code == 0 {
		t.Fatalf("expected to fail, but succeeded. \n\n%s", output.All())
	}

	expectedTitle := "Can't change variable when applying a saved plan"
	if !strings.Contains(output.Stderr(), expectedTitle) {
		t.Fatalf("Expected stderr to contain %q, got %q", expectedTitle, output.Stderr())
	}
}

func TestApply_planUndeclaredVars(t *testing.T) {
	// This test ensures that it isn't allowed to set undeclared input variables
	// when applying from a saved plan file, since in that case the variable
	// values come from the saved plan file.

	planPath := applyFixturePlanFile(t)
	statePath := testTempFile(t)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-var", "foo=bar",
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code == 0 {
		t.Fatal("should've failed: ", output.Stdout())
	}
}

func TestApply_planWithEnvVars(t *testing.T) {
	_, snap := testModuleWithSnapshot(t, "apply-output-only")
	plan := testPlan(t)

	addr, diags := addrs.ParseAbsOutputValueStr("output.shadow")
	if diags.HasErrors() {
		t.Fatal(diags.Err())
	}

	shadowVal := mustNewDynamicValue("noot", cty.DynamicPseudoType)
	plan.VariableValues = map[string]plans.DynamicValue{
		"shadow": shadowVal,
	}
	plan.Changes.Outputs = append(plan.Changes.Outputs, &plans.OutputChangeSrc{
		Addr: addr,
		ChangeSrc: plans.ChangeSrc{
			Action: plans.Create,
			After:  shadowVal,
		},
	})
	planPath := testPlanFileMatchState(
		t,
		snap,
		states.NewState(),
		plan,
		statemgr.SnapshotMeta{},
	)

	statePath := testTempFile(t)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	t.Setenv("TF_VAR_shadow", "env")

	args := []string{
		"-state", statePath,
		"-no-color",
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatal("unexpected failure: ", output.All())
	}

	expectedWarn := "Warning: Ignoring variable when applying a saved plan\n"
	if !strings.Contains(output.Stdout(), expectedWarn) {
		t.Fatalf("expected warning in output, given: %q", output.Stdout())
	}
}

func TestApply_planWithSensitiveEnvVars(t *testing.T) {
	_, snap := testModuleWithSnapshot(t, "apply-sensitive-variable")
	plan := testPlan(t)

	addr, diags := addrs.ParseAbsOutputValueStr("output.shadow")
	if diags.HasErrors() {
		t.Fatal(diags.Err())
	}

	shadowVal := mustNewDynamicValue("noot", cty.DynamicPseudoType)
	plan.VariableValues = map[string]plans.DynamicValue{
		"shadow": shadowVal,
	}
	plan.Changes.Outputs = append(plan.Changes.Outputs, &plans.OutputChangeSrc{
		Addr: addr,
		ChangeSrc: plans.ChangeSrc{
			Action: plans.Create,
			After:  shadowVal,
		},
	})
	planPath := testPlanFileMatchState(
		t,
		snap,
		states.NewState(),
		plan,
		statemgr.SnapshotMeta{},
	)

	statePath := testTempFile(t)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	t.Setenv("TF_VAR_shadow", "unique")

	args := []string{
		"-state", statePath,
		"-no-color",
		planPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatal("unexpected failure: ", output.All())
	}

	out := output.Stdout()

	expectedWarn := "Warning: Ignoring variable when applying a saved plan\n"
	if !strings.Contains(out, expectedWarn) {
		t.Fatalf("expected warning in output, given: %q", out)
	}

	if !strings.Contains(out, "(sensitive value)") {
		t.Error("should have elided sensitive value")
	}

	if strings.Contains(out, "noot") {
		t.Error("should have elided sensitive input, but contained value")
	}

	if strings.Contains(out, "unique") {
		t.Error("should have elided sensitive input, but contained value")
	}
}

// A saved plan includes a list of "apply-time variables", i.e. ephemeral
// input variables that were set during the plan, and must therefore be set
// during apply. No other variables may be set during apply.
//
// Test that an apply supplying all apply-time variables succeeds, and then test
// that supplying a declared ephemeral input variable that is *not* in the list
// of apply-time variables fails.
//
// In the fixture used for this test foo is a required ephemeral variable, whereas bar is
// an optional one.
func TestApply_planVarsEphemeral_applyTime(t *testing.T) {
	for name, tc := range map[string]func(*testing.T, *ApplyCommand, string, string, func(*testing.T) *terminal.TestOutput){
		"with planfile only passing ephemeral variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			args := []string{
				"-state", statePath,
				"-var", "foo=bar",
				planPath,
			}
			code := c.Run(args)
			output := done(t)
			if code != 0 {
				t.Fatal("should've succeeded: ", output.All())
			}
		},

		"with planfile passing non-ephemeral variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			args := []string{
				"-state", statePath,
				"-var", "foo=bar",
				"-var", "bar=bar",
				planPath,
			}
			code := c.Run(args)
			output := done(t)
			if code == 0 {
				t.Fatal("should've failed: ", output.All())
			}
		},

		"with planfile missing ephemeral variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			args := []string{
				"-state", statePath,
				planPath,
			}
			code := c.Run(args)
			output := done(t)
			if code == 0 {
				t.Fatal("should've failed: ", output.All())
			}
		},

		"with planfile passing ephemeral variable through vars file": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			const planVarFile = `
foo = "bar"
`

			// Write a tfvars file with the variable
			tfVarsPath := testVarsFile(t)
			err := os.WriteFile(tfVarsPath, []byte(planVarFile), 0600)
			if err != nil {
				t.Fatalf("Could not write vars file %e", err)
			}

			args := []string{
				"-state", statePath,
				"-var-file", tfVarsPath,
				planPath,
			}
			code := c.Run(args)
			output := done(t)
			if code != 0 {
				t.Fatal("should've succeeded: ", output.All())
			}
		},

		"with planfile passing ephemeral variable through environment variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			t.Setenv("TF_VAR_foo", "bar")

			args := []string{
				"-state", statePath,
				planPath,
			}
			code := c.Run(args)
			output := done(t)
			if code != 0 {
				t.Fatal("should've succeeded: ", output.All())
			}
		},

		"with planfile passing ephemeral variable through interactive prompts": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			close := testInteractiveInput(t, []string{"bar"})
			defer close()

			args := []string{
				"-state", statePath,
				planPath,
			}
			code := c.Run(args)
			output := done(t)
			if code == 0 {
				// We don't support interactive inputs for apply-time variables
				t.Fatal("should have failed: ", output.All())
			}
		},

		"without planfile only passing ephemeral variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			args := []string{
				"-state", statePath,
				"-var", "foo=bar",
			}
			code := c.Run(args)
			output := done(t)
			if code != 0 {
				t.Fatal("should've succeeded: ", output.All())
			}
		},

		"without planfile passing non-ephemeral variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			args := []string{
				"-state", statePath,
				"-var", "foo=bar",
				"-var", "bar=bar",
			}
			code := c.Run(args)
			output := done(t)

			// For a combined plan & apply operation it's okay (and expected) to also be able to pass non-ephemeral variables
			if code != 0 {
				t.Fatal("should've succeeded: ", output.All())
			}
		},

		"without planfile missing ephemeral variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			args := []string{
				"-state", statePath,
			}
			code := c.Run(args)
			output := done(t)
			if code == 0 {
				t.Fatal("should've failed: ", output.All())
			}
		},

		"without planfile passing ephemeral variable through vars file": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			const planVarFile = `
foo = "bar"
`

			// Write a tfvars file with the variable
			tfVarsPath := testVarsFile(t)
			err := os.WriteFile(tfVarsPath, []byte(planVarFile), 0600)
			if err != nil {
				t.Fatalf("Could not write vars file %e", err)
			}

			args := []string{
				"-state", statePath,
				"-var-file", tfVarsPath,
			}
			code := c.Run(args)
			output := done(t)
			if code != 0 {
				t.Fatal("should've succeeded: ", output.All())
			}
		},

		"without planfile passing ephemeral variable through environment variable": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			t.Setenv("TF_VAR_foo", "bar")
			t.Setenv("TF_VAR_unused", `{key:"val"}`)

			args := []string{
				"-state", statePath,
			}
			code := c.Run(args)
			output := done(t)
			if code != 0 {
				t.Fatal("should've succeeded: ", output.All())
			}
		},

		"without planfile passing ephemeral variable through interactive prompts": func(t *testing.T, c *ApplyCommand, statePath, planPath string, done func(*testing.T) *terminal.TestOutput) {
			close := testInteractiveInput(t, []string{"bar"})
			defer close()

			args := []string{
				"-state", statePath,
			}
			code := c.Run(args)
			output := done(t)
			if code != 0 {
				t.Fatal("should've succeeded: ", output.All())
			}
		},
	} {
		t.Run(name, func(t *testing.T) {
			td := t.TempDir()
			testCopyDir(t, testFixturePath("apply-ephemeral-variable"), td)
			t.Chdir(td)

			_, snap := testModuleWithSnapshot(t, "apply-ephemeral-variable")
			plannedVal := cty.ObjectVal(map[string]cty.Value{
				"id":  cty.UnknownVal(cty.String),
				"ami": cty.StringVal("bar"),
			})
			priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type())
			if err != nil {
				t.Fatal(err)
			}
			plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type())
			if err != nil {
				t.Fatal(err)
			}
			plan := testPlan(t)
			plan.Changes.AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{
				Addr: addrs.Resource{
					Mode: addrs.ManagedResourceMode,
					Type: "test_instance",
					Name: "foo",
				}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
				ProviderAddr: addrs.AbsProviderConfig{
					Provider: addrs.NewDefaultProvider("test"),
					Module:   addrs.RootModule,
				},
				ChangeSrc: plans.ChangeSrc{
					Action: plans.Create,
					Before: priorValRaw,
					After:  plannedValRaw,
				},
			})
			applyTimeVariables := collections.NewSetCmp[string]()
			applyTimeVariables.Add("foo")
			plan.ApplyTimeVariables = applyTimeVariables

			planPath := testPlanFileMatchState(
				t,
				snap,
				states.NewState(),
				plan,
				statemgr.SnapshotMeta{},
			)

			statePath := testTempFile(t)

			p := applyFixtureProvider()
			view, done := testView(t)
			c := &ApplyCommand{
				Meta: Meta{
					testingOverrides: metaOverridesForProvider(p),
					View:             view,
				},
			}

			tc(t, c, statePath, planPath, done)
		})
	}
}

// Variables can be passed to apply now for ephemeral usage, but we need to
// ensure that the legacy handling of undeclared variables remains intact
func TestApply_changedVars_applyTime(t *testing.T) {
	t.Run("undeclared-config-var", func(t *testing.T) {
		// an undeclared config variable is a warning, just like during plan
		varFileDir := testTempDir(t)
		varFilePath := filepath.Join(varFileDir, "terraform.tfvars")
		if err := os.WriteFile(varFilePath, []byte(`undeclared = true`), 0644); err != nil {
			t.Fatalf("err: %s", err)
		}

		// The value of foo is not set
		planPath := applyFixturePlanFile(t)
		statePath := testTempFile(t)

		cwd, err := os.Getwd()
		if err != nil {
			t.Fatalf("err: %s", err)
		}
		if err := os.Chdir(varFileDir); err != nil {
			t.Fatalf("err: %s", err)
		}
		defer os.Chdir(cwd)

		p := applyFixtureProvider()
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		args := []string{
			"-state-out", statePath,
			planPath,
		}
		code := c.Run(args)
		output := done(t)
		if code != 0 {
			t.Fatalf("unexpected exit code %d:\n\n%s", code, output.All())
		}

		if !strings.Contains(output.All(), `Value for undeclared variable`) {
			t.Fatalf("missing undeclared warning:\n%s", output.All())
		}
	})

	t.Run("undeclared-cli-var", func(t *testing.T) {
		// an undeclared cli variable is an error, just like during plan
		planPath := applyFixturePlanFile(t)
		statePath := testTempFile(t)

		p := applyFixtureProvider()
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		args := []string{
			"-var", "undeclared=true",
			"-state-out", statePath,
			planPath,
		}
		code := c.Run(args)
		output := done(t)
		if code != 1 {
			t.Fatalf("unexpected exit code %d:\n\n%s", code, output.All())
		}

		if !strings.Contains(output.Stderr(), `Value for undeclared variable`) {
			t.Fatalf("missing undeclared warning:\n%s", output.All())
		}
	})

	t.Run("changed-cli-var", func(t *testing.T) {
		planPath := applyFixturePlanFileWithVariableValue(t, "orig")
		statePath := testTempFile(t)

		p := applyFixtureProvider()
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		args := []string{
			"-var", "foo=new",
			"-state-out", statePath,
			planPath,
		}
		code := c.Run(args)
		output := done(t)
		if code != 1 {
			t.Fatalf("unexpected exit code %d:\n\n%s", code, output.All())
		}

		if !strings.Contains(output.Stderr(), `Can't change variable when applying a saved plan`) {
			t.Fatalf("missing undeclared warning:\n%s", output.All())
		}
	})

	t.Run("var-file-override-auto", func(t *testing.T) {
		// for this one we're going to do a full plan to make sure the variables
		// can be applied consistently. The plan specifies a var file, and
		// during apply we don't want to override that with the default or auto
		// var files.
		td := t.TempDir()
		testCopyDir(t, testFixturePath("apply-vars-auto"), td)
		t.Chdir(td)

		p := planVarsFixtureProvider()
		view, done := testView(t)
		c := &PlanCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		args := []string{
			"-var-file", "terraform-test.tfvars",
			"-out", "planfile",
		}
		code := c.Run(args)
		output := done(t)
		if code != 0 {
			t.Fatalf("non-zero exit %d\n\n%s", code, output.Stderr())
		}

		view, done = testView(t)
		apply := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				Ui:               new(cli.MockUi),
				View:             view,
			},
		}
		args = []string{
			"planfile",
		}
		code = apply.Run(args)
		output = done(t)
		if code != 0 {
			t.Fatalf("non-zero exit %d\n\n%s", code, output.Stderr())
		}
	})
}

// There isn't validation to stop users passing plan options like -target, -destroy and -refresh-only flags
// to an apply command when a saved plan is used.
//
// Adding any validation is a breaking change.
// For now, we raise warnings when applying a plan file that doesn't match the intent
// expressed by the plan options passed to the apply command alongside the plan file.
// See: https://github.com/hashicorp/terraform/issues/38670
func TestApply_changedPlanOptions_applyTime(t *testing.T) {
	t.Run("change to -target", func(t *testing.T) {
		planPath := applyFixturePlanFile(t)
		statePath := testTempFile(t)

		p := applyFixtureProvider()
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		args := []string{
			"-no-color",
			"-target", "test_instance.foo",
			"-state-out", statePath,
			planPath,
		}
		code := c.Run(args)
		output := done(t)
		if code != 0 {
			t.Fatalf("unexpected exit code %d:\n\n%s", code, output.All())
		}

		if !strings.Contains(output.Stdout(), `Warning: Can't change resource targeting when applying a saved plan`) {
			t.Fatalf("missing warning text:\n%s", output.All())
		}
		if !strings.Contains(output.Stdout(), `target address "test_instance.foo"`) {
			t.Fatalf("missing detail from warning:\n%s", output.All())
		}
	})
	t.Run("change to -destroy", func(t *testing.T) {
		planPath := applyFixturePlanFile(t)
		statePath := testTempFile(t)

		p := applyFixtureProvider()
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		args := []string{
			"-no-color",
			"-destroy",
			"-state-out", statePath,
			planPath,
		}
		code := c.Run(args)
		output := done(t)
		if code != 0 {
			t.Fatalf("unexpected exit code %d:\n\n%s", code, output.All())
		}

		if !strings.Contains(output.Stdout(), `Warning: Can't change plan mode when applying a saved plan`) {
			t.Fatalf("missing warning:\n%s", output.All())
		}
		if !strings.Contains(output.Stdout(), `-destroy`) {
			t.Fatalf("missing flag name from warning:\n%s", output.All())
		}
	})
	t.Run("change to -refresh-only", func(t *testing.T) {
		planPath := applyFixturePlanFile(t)
		statePath := testTempFile(t)

		p := applyFixtureProvider()
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		args := []string{
			"-no-color",
			"-refresh-only",
			"-state-out", statePath,
			planPath,
		}
		code := c.Run(args)
		output := done(t)
		if code != 0 {
			t.Fatalf("unexpected exit code %d:\n\n%s", code, output.All())
		}

		if !strings.Contains(output.Stdout(), `Warning: Can't change plan mode when applying a saved plan`) {
			t.Fatalf("missing warning text:\n%s", output.All())
		}
		if !strings.Contains(output.Stdout(), `-refresh-only`) {
			t.Fatalf("missing flag name from warning:\n%s", output.All())
		}
	})
}

// we should be able to apply a plan file with no other file dependencies
func TestApply_planNoModuleFiles(t *testing.T) {
	// temporary data directory which we can remove between commands
	td := testTempDir(t)
	defer os.RemoveAll(td)

	t.Chdir(td)

	p := applyFixtureProvider()
	planPath := applyFixturePlanFile(t)
	view, done := testView(t)
	apply := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			Ui:               new(cli.MockUi),
			View:             view,
		},
	}
	args := []string{
		planPath,
	}
	apply.Run(args)
	done(t)
}

func TestApply_refresh(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	originalState := states.BuildState(func(s *states.SyncState) {
		s.SetResourceInstanceCurrent(
			addrs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "test_instance",
				Name: "foo",
			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
			&states.ResourceInstanceObjectSrc{
				AttrsJSON: []byte(`{"ami":"bar"}`),
				Status:    states.ObjectReady,
			},
			addrs.AbsProviderConfig{
				Provider: addrs.NewDefaultProvider("test"),
				Module:   addrs.RootModule,
			},
		)
	})
	statePath := testStateFile(t, originalState)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if !p.ReadResourceCalled {
		t.Fatal("should call ReadResource")
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}

	// Should have a backup file
	backupState := testStateRead(t, statePath+DefaultBackupExtension)

	actualStr := strings.TrimSpace(backupState.String())
	expectedStr := strings.TrimSpace(originalState.String())
	if actualStr != expectedStr {
		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
	}
}

func TestApply_refreshFalse(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	originalState := states.BuildState(func(s *states.SyncState) {
		s.SetResourceInstanceCurrent(
			addrs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "test_instance",
				Name: "foo",
			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
			&states.ResourceInstanceObjectSrc{
				AttrsJSON: []byte(`{"ami":"bar"}`),
				Status:    states.ObjectReady,
			},
			addrs.AbsProviderConfig{
				Provider: addrs.NewDefaultProvider("test"),
				Module:   addrs.RootModule,
			},
		)
	})
	statePath := testStateFile(t, originalState)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
		"-refresh=false",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if p.ReadResourceCalled {
		t.Fatal("should not call ReadResource when refresh=false")
	}
}

func TestApply_shutdown(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-shutdown"), td)
	t.Chdir(td)

	cancelled := make(chan struct{})
	shutdownCh := make(chan struct{})

	statePath := testTempFile(t)
	p := testProvider()

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
			ShutdownCh:       shutdownCh,
		},
	}

	p.StopFn = func() error {
		close(cancelled)
		return nil
	}

	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
		resp.PlannedState = req.ProposedNewState
		return
	}

	var once sync.Once
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
		// only cancel once
		once.Do(func() {
			shutdownCh <- struct{}{}
		})

		// Because of the internal lock in the MockProvider, we can't
		// coordiante directly with the calling of Stop, and making the
		// MockProvider concurrent is disruptive to a lot of existing tests.
		// Wait here a moment to help make sure the main goroutine gets to the
		// Stop call before we exit, or the plan may finish before it can be
		// canceled.
		time.Sleep(200 * time.Millisecond)

		resp.NewState = req.PlannedState
		return
	}

	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"ami": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	select {
	case <-cancelled:
	default:
		t.Fatal("command not cancelled")
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}
}

func TestApply_state(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	originalState := states.BuildState(func(s *states.SyncState) {
		s.SetResourceInstanceCurrent(
			addrs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "test_instance",
				Name: "foo",
			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
			&states.ResourceInstanceObjectSrc{
				AttrsJSON: []byte(`{"ami":"foo"}`),
				Status:    states.ObjectReady,
			},
			addrs.AbsProviderConfig{
				Provider: addrs.NewDefaultProvider("test"),
				Module:   addrs.RootModule,
			},
		)
	})
	statePath := testStateFile(t, originalState)

	p := applyFixtureProvider()
	p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{
		PlannedState: cty.ObjectVal(map[string]cty.Value{
			"ami": cty.StringVal("bar"),
		}),
	}
	p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{
		NewState: cty.ObjectVal(map[string]cty.Value{
			"ami": cty.StringVal("bar"),
		}),
	}

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	// Run the apply command pointing to our existing state
	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Verify that the provider was called with the existing state
	actual := p.PlanResourceChangeRequest.PriorState
	expected := cty.ObjectVal(map[string]cty.Value{
		"id":  cty.NullVal(cty.String),
		"ami": cty.StringVal("foo"),
	})
	if !expected.RawEquals(actual) {
		t.Fatalf("wrong prior state during plan\ngot: %#v\nwant: %#v", actual, expected)
	}

	actual = p.ApplyResourceChangeRequest.PriorState
	expected = cty.ObjectVal(map[string]cty.Value{
		"id":  cty.NullVal(cty.String),
		"ami": cty.StringVal("foo"),
	})
	if !expected.RawEquals(actual) {
		t.Fatalf("wrong prior state during apply\ngot: %#v\nwant: %#v", actual, expected)
	}

	// Verify a new state exists
	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}

	backupState := testStateRead(t, statePath+DefaultBackupExtension)

	actualStr := strings.TrimSpace(backupState.String())
	expectedStr := strings.TrimSpace(originalState.String())
	if actualStr != expectedStr {
		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
	}
}

func TestApply_stateNoExist(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	p := applyFixtureProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"idontexist.tfstate",
	}
	code := c.Run(args)
	output := done(t)
	if code != 1 {
		t.Fatalf("bad: \n%s", output.Stdout())
	}
}

func TestApply_sensitiveOutput(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-sensitive-output"), td)
	t.Chdir(td)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	statePath := testTempFile(t)

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}

	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: \n%s", output.Stdout())
	}

	stdout := output.Stdout()
	if !strings.Contains(stdout, "notsensitive = \"Hello world\"") {
		t.Fatalf("bad: output should contain 'notsensitive' output\n%s", stdout)
	}
	if !strings.Contains(stdout, "sensitive = <sensitive>") {
		t.Fatalf("bad: output should contain 'sensitive' output\n%s", stdout)
	}
}

func TestApply_vars(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-vars"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	actual := ""
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"value": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		return providers.ApplyResourceChangeResponse{
			NewState: req.PlannedState,
		}
	}
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		actual = req.ProposedNewState.GetAttr("value").AsString()
		return providers.PlanResourceChangeResponse{
			PlannedState: req.ProposedNewState,
		}
	}

	args := []string{
		"-auto-approve",
		"-var", "foo=bar",
		"-state", statePath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if actual != "bar" {
		t.Fatal("didn't work")
	}
}

func TestApply_varFile(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-vars"), td)
	t.Chdir(td)

	varFilePath := testTempFile(t)
	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
		t.Fatalf("err: %s", err)
	}

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	actual := ""
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"value": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		return providers.ApplyResourceChangeResponse{
			NewState: req.PlannedState,
		}
	}
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		actual = req.ProposedNewState.GetAttr("value").AsString()
		return providers.PlanResourceChangeResponse{
			PlannedState: req.ProposedNewState,
		}
	}

	args := []string{
		"-auto-approve",
		"-var-file", varFilePath,
		"-state", statePath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if actual != "bar" {
		t.Fatal("didn't work")
	}
}

func TestApply_varFileDefault(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-vars"), td)
	t.Chdir(td)

	varFilePath := filepath.Join(td, "terraform.tfvars")
	if err := os.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
		t.Fatalf("err: %s", err)
	}

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	actual := ""
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"value": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		return providers.ApplyResourceChangeResponse{
			NewState: req.PlannedState,
		}
	}
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		actual = req.ProposedNewState.GetAttr("value").AsString()
		return providers.PlanResourceChangeResponse{
			PlannedState: req.ProposedNewState,
		}
	}

	args := []string{
		"-auto-approve",
		"-state", statePath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if actual != "bar" {
		t.Fatal("didn't work")
	}
}

func TestApply_varFileDefaultJSON(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-vars"), td)
	t.Chdir(td)

	varFilePath := filepath.Join(td, "terraform.tfvars.json")
	if err := ioutil.WriteFile(varFilePath, []byte(applyVarFileJSON), 0644); err != nil {
		t.Fatalf("err: %s", err)
	}

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	actual := ""
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"value": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		return providers.ApplyResourceChangeResponse{
			NewState: req.PlannedState,
		}
	}
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		actual = req.ProposedNewState.GetAttr("value").AsString()
		return providers.PlanResourceChangeResponse{
			PlannedState: req.ProposedNewState,
		}
	}

	args := []string{
		"-auto-approve",
		"-state", statePath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if actual != "bar" {
		t.Fatal("didn't work")
	}
}

func TestApply_backup(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	originalState := states.BuildState(func(s *states.SyncState) {
		s.SetResourceInstanceCurrent(
			addrs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "test_instance",
				Name: "foo",
			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
			&states.ResourceInstanceObjectSrc{
				AttrsJSON: []byte("{\n            \"id\": \"bar\"\n          }"),
				Status:    states.ObjectReady,
			},
			addrs.AbsProviderConfig{
				Provider: addrs.NewDefaultProvider("test"),
				Module:   addrs.RootModule,
			},
		)
	})
	statePath := testStateFile(t, originalState)
	backupPath := testTempFile(t)

	p := applyFixtureProvider()
	p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{
		PlannedState: cty.ObjectVal(map[string]cty.Value{
			"ami": cty.StringVal("bar"),
		}),
	}

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	// Run the apply command pointing to our existing state
	args := []string{
		"-auto-approve",
		"-state", statePath,
		"-backup", backupPath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Verify a new state exists
	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}

	backupState := testStateRead(t, backupPath)

	actual := backupState.RootModule().Resources["test_instance.foo"]
	expected := originalState.RootModule().Resources["test_instance.foo"]
	if !cmp.Equal(actual, expected, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(states.ResourceInstanceObjectSrc{})) {
		t.Fatalf(
			"wrong aws_instance.foo state\n%s",
			cmp.Diff(expected, actual, cmp.Transformer("bytesAsString", func(b []byte) string {
				return string(b)
			})),
		)
	}
}

func TestApply_disableBackup(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	originalState := testState()
	statePath := testStateFile(t, originalState)

	p := applyFixtureProvider()
	p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{
		PlannedState: cty.ObjectVal(map[string]cty.Value{
			"ami": cty.StringVal("bar"),
		}),
	}

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	// Run the apply command pointing to our existing state
	args := []string{
		"-auto-approve",
		"-state", statePath,
		"-backup", "-",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	// Verify that the provider was called with the existing state
	actual := p.PlanResourceChangeRequest.PriorState
	expected := cty.ObjectVal(map[string]cty.Value{
		"id":  cty.StringVal("bar"),
		"ami": cty.NullVal(cty.String),
	})
	if !expected.RawEquals(actual) {
		t.Fatalf("wrong prior state during plan\ngot:  %#v\nwant: %#v", actual, expected)
	}

	actual = p.ApplyResourceChangeRequest.PriorState
	expected = cty.ObjectVal(map[string]cty.Value{
		"id":  cty.StringVal("bar"),
		"ami": cty.NullVal(cty.String),
	})
	if !expected.RawEquals(actual) {
		t.Fatalf("wrong prior state during apply\ngot:  %#v\nwant: %#v", actual, expected)
	}

	// Verify a new state exists
	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}

	// Ensure there is no backup
	_, err := os.Stat(statePath + DefaultBackupExtension)
	if err == nil || !os.IsNotExist(err) {
		t.Fatalf("backup should not exist")
	}

	// Ensure there is no literal "-"
	_, err = os.Stat("-")
	if err == nil || !os.IsNotExist(err) {
		t.Fatalf("backup should not exist")
	}
}

// Test that the Terraform env is passed through
func TestApply_terraformEnv(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-terraform-env"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-auto-approve",
		"-state", statePath,
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	expected := strings.TrimSpace(`
<no state>
Outputs:

output = default
	`)
	testStateOutput(t, statePath, expected)
}

// Test that the Terraform env is passed through
func TestApply_terraformEnvNonDefault(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-terraform-env"), td)
	t.Chdir(td)

	// Create new env
	{
		ui := new(cli.MockUi)
		newCmd := &WorkspaceNewCommand{
			Meta: Meta{
				Ui: ui,
			},
		}
		if code := newCmd.Run([]string{"test"}); code != 0 {
			t.Fatal("error creating workspace")
		}
	}

	// Switch to it
	{
		args := []string{"test"}
		ui := new(cli.MockUi)
		selCmd := &WorkspaceSelectCommand{
			Meta: Meta{
				Ui: ui,
			},
		}
		if code := selCmd.Run(args); code != 0 {
			t.Fatal("error switching workspace")
		}
	}

	p := testProvider()
	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	statePath := filepath.Join("terraform.tfstate.d", "test", "terraform.tfstate")
	expected := strings.TrimSpace(`
<no state>
Outputs:

output = test
	`)
	testStateOutput(t, statePath, expected)
}

// Config with multiple resources, targeting apply of a subset
func TestApply_targeted(t *testing.T) {
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-targeted"), td)
	t.Chdir(td)

	p := testProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Computed: true},
					},
				},
			},
		},
	}
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		return providers.PlanResourceChangeResponse{
			PlannedState: req.ProposedNewState,
		}
	}

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-auto-approve",
		"-target", "test_instance.foo",
		"-target", "test_instance.baz",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if got, want := output.Stdout(), "3 added, 0 changed, 0 destroyed"; !strings.Contains(got, want) {
		t.Fatalf("bad change summary, want %q, got:\n%s", want, got)
	}
}

// Diagnostics for invalid -target flags
func TestApply_targetFlagsDiags(t *testing.T) {
	testCases := map[string]string{
		"test_instance.": "Dot must be followed by attribute name.",
		"test_instance":  "Resource specification must include a resource type and name.",
	}

	for target, wantDiag := range testCases {
		t.Run(target, func(t *testing.T) {
			td := testTempDir(t)
			defer os.RemoveAll(td)
			t.Chdir(td)

			view, done := testView(t)
			c := &ApplyCommand{
				Meta: Meta{
					View: view,
				},
			}

			args := []string{
				"-auto-approve",
				"-target", target,
			}
			code := c.Run(args)
			output := done(t)
			if code != 1 {
				t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
			}

			got := output.Stderr()
			if !strings.Contains(got, target) {
				t.Fatalf("bad error output, want %q, got:\n%s", target, got)
			}
			if !strings.Contains(got, wantDiag) {
				t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got)
			}
		})
	}
}

func TestApply_replace(t *testing.T) {
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply-replace"), td)
	t.Chdir(td)

	originalState := states.BuildState(func(s *states.SyncState) {
		s.SetResourceInstanceCurrent(
			addrs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "test_instance",
				Name: "a",
			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
			&states.ResourceInstanceObjectSrc{
				AttrsJSON: []byte(`{"id":"hello"}`),
				Status:    states.ObjectReady,
			},
			addrs.AbsProviderConfig{
				Provider: addrs.NewDefaultProvider("test"),
				Module:   addrs.RootModule,
			},
		)
	})
	statePath := testStateFile(t, originalState)

	p := testProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Computed: true},
					},
				},
			},
		},
	}
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		return providers.PlanResourceChangeResponse{
			PlannedState: req.ProposedNewState,
		}
	}
	createCount := 0
	deleteCount := 0
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		if req.PriorState.IsNull() {
			createCount++
		}
		if req.PlannedState.IsNull() {
			deleteCount++
		}
		return providers.ApplyResourceChangeResponse{
			NewState: req.PlannedState,
		}
	}

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-auto-approve",
		"-state", statePath,
		"-replace", "test_instance.a",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("wrong exit code %d\n\n%s", code, output.Stderr())
	}

	if got, want := output.Stdout(), "1 added, 0 changed, 1 destroyed"; !strings.Contains(got, want) {
		t.Errorf("wrong change summary\ngot output:\n%s\n\nwant substring: %s", got, want)
	}

	if got, want := createCount, 1; got != want {
		t.Errorf("wrong create count %d; want %d", got, want)
	}
	if got, want := deleteCount, 1; got != want {
		t.Errorf("wrong create count %d; want %d", got, want)
	}
}

func TestApply_pluginPath(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := applyFixtureProvider()

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	pluginPath := []string{"a", "b", "c"}

	if err := c.Meta.storePluginPath(pluginPath); err != nil {
		t.Fatal(err)
	}
	c.Meta.pluginPath = nil

	args := []string{
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if !reflect.DeepEqual(pluginPath, c.Meta.pluginPath) {
		t.Fatalf("expected plugin path %#v, got %#v", pluginPath, c.Meta.pluginPath)
	}
}

func TestApply_jsonGoldenReference(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	statePath := testTempFile(t)

	p := applyFixtureProvider()

	view, done := testView(t)
	c := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-json",
		"-state", statePath,
		"-auto-approve",
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
	}

	if _, err := os.Stat(statePath); err != nil {
		t.Fatalf("err: %s", err)
	}

	state := testStateRead(t, statePath)
	if state == nil {
		t.Fatal("state should not be nil")
	}

	checkGoldenReference(t, output, "apply")
}

func TestApply_warnings(t *testing.T) {
	// Create a temporary working directory that is empty
	td := t.TempDir()
	testCopyDir(t, testFixturePath("apply"), td)
	t.Chdir(td)

	p := testProvider()
	p.GetProviderSchemaResponse = applyFixtureSchema()
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		return providers.PlanResourceChangeResponse{
			PlannedState: req.ProposedNewState,
			Diagnostics: tfdiags.Diagnostics{
				tfdiags.SimpleWarning("warning 1"),
				tfdiags.SimpleWarning("warning 2"),
			},
		}
	}
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		return providers.ApplyResourceChangeResponse{
			NewState: cty.UnknownAsNull(req.PlannedState),
		}
	}

	t.Run("full warnings", func(t *testing.T) {
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		args := []string{"-auto-approve"}
		code := c.Run(args)
		output := done(t)
		if code != 0 {
			t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
		}
		wantWarnings := []string{
			"warning 1",
			"warning 2",
		}
		for _, want := range wantWarnings {
			if !strings.Contains(output.Stdout(), want) {
				t.Errorf("missing warning %s", want)
			}
		}
	})

	t.Run("compact warnings", func(t *testing.T) {
		view, done := testView(t)
		c := &ApplyCommand{
			Meta: Meta{
				testingOverrides: metaOverridesForProvider(p),
				View:             view,
			},
		}

		code := c.Run([]string{"-auto-approve", "-compact-warnings"})
		output := done(t)
		if code != 0 {
			t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
		}
		// the output should contain 2 warnings and a message about -compact-warnings
		wantWarnings := []string{
			"warning 1",
			"warning 2",
			"To see the full warning notes, run Terraform without -compact-warnings.",
		}
		for _, want := range wantWarnings {
			if !strings.Contains(output.Stdout(), want) {
				t.Errorf("missing warning %s", want)
			}
		}
	})
}

// applyFixtureSchema returns a schema suitable for processing the
// configuration in testdata/apply . This schema should be
// assigned to a mock provider named "test".
func applyFixtureSchema() *providers.GetProviderSchemaResponse {
	return &providers.GetProviderSchemaResponse{
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id":  {Type: cty.String, Optional: true, Computed: true},
						"ami": {Type: cty.String, Optional: true},
					},
				},
			},
		},
	}
}

// applyFixtureProvider returns a mock provider that is configured for basic
// operation with the configuration in testdata/apply. This mock has
// GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated,
// with the plan/apply steps just passing through the data determined by
// Terraform Core.
func applyFixtureProvider() *testing_provider.MockProvider {
	p := testProvider()
	p.GetProviderSchemaResponse = applyFixtureSchema()
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		return providers.PlanResourceChangeResponse{
			PlannedState: req.ProposedNewState,
		}
	}
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		return providers.ApplyResourceChangeResponse{
			NewState: cty.UnknownAsNull(req.PlannedState),
		}
	}
	return p
}

// applyFixturePlanFile creates a plan file at a temporary location containing
// a single change to create the test_instance.foo that is included in the
// "apply" test fixture, returning the location of that plan file.
func applyFixturePlanFile(t *testing.T) string {
	return applyFixturePlanFileMatchState(t, statemgr.SnapshotMeta{})
}

// applyFixturePlanFileMatchState creates a planfile like applyFixturePlanFile,
// but inserts the state meta information if that plan must match a preexisting
// state.
func applyFixturePlanFileMatchState(t *testing.T, stateMeta statemgr.SnapshotMeta) string {
	_, snap := testModuleWithSnapshot(t, "apply")
	plannedVal := cty.ObjectVal(map[string]cty.Value{
		"id":  cty.UnknownVal(cty.String),
		"ami": cty.StringVal("bar"),
	})
	priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type())
	if err != nil {
		t.Fatal(err)
	}
	plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type())
	if err != nil {
		t.Fatal(err)
	}
	plan := testPlan(t)
	plan.Changes.AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{
		Addr: addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "test_instance",
			Name: "foo",
		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
		ProviderAddr: addrs.AbsProviderConfig{
			Provider: addrs.NewDefaultProvider("test"),
			Module:   addrs.RootModule,
		},
		ChangeSrc: plans.ChangeSrc{
			Action: plans.Create,
			Before: priorValRaw,
			After:  plannedValRaw,
		},
	})
	return testPlanFileMatchState(
		t,
		snap,
		states.NewState(),
		plan,
		stateMeta,
	)
}

// applyFixturePlanFileWithVariableValue creates a plan file at a temporary location containing
// a single change to create the test_instance.foo and a variable value that is included in the
// "apply" test fixture, returning the location of that plan file.
func applyFixturePlanFileWithVariableValue(t *testing.T, value string) string {
	_, snap := testModuleWithSnapshot(t, "apply-vars")
	plannedVal := cty.ObjectVal(map[string]cty.Value{
		"id":    cty.UnknownVal(cty.String),
		"value": cty.StringVal("bar"),
	})
	priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type())
	if err != nil {
		t.Fatal(err)
	}
	plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type())
	if err != nil {
		t.Fatal(err)
	}
	plan := testPlan(t)
	plan.Changes.AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{
		Addr: addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "test_instance",
			Name: "foo",
		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
		ProviderAddr: addrs.AbsProviderConfig{
			Provider: addrs.NewDefaultProvider("test"),
			Module:   addrs.RootModule,
		},
		ChangeSrc: plans.ChangeSrc{
			Action: plans.Create,
			Before: priorValRaw,
			After:  plannedValRaw,
		},
	})

	plan.VariableValues = map[string]plans.DynamicValue{
		"foo": mustNewDynamicValue(value, cty.DynamicPseudoType),
	}
	return testPlanFileMatchState(
		t,
		snap,
		states.NewState(),
		plan,
		statemgr.SnapshotMeta{},
	)
}

const applyVarFile = `
foo = "bar"
`

const applyVarFileJSON = `
{ "foo": "bar" }
`

func mustNewDynamicValue(val string, ty cty.Type) plans.DynamicValue {
	realVal := cty.StringVal(val)
	ret, err := plans.NewDynamicValue(realVal, ty)
	if err != nil {
		panic(err)
	}
	return ret
}

func TestProviderInconsistentFileFunc(t *testing.T) {
	// Verify that providers can still accept inconsistent results from
	// filesystem functions. We allow this for backwards compatibility, but
	// ephemeral values should be used in the long-term to allow for controlled
	// changes in values between plan and apply.
	td := t.TempDir()
	planDir := filepath.Join(td, "plan")
	applyDir := filepath.Join(td, "apply")
	testCopyDir(t, testFixturePath("changed-file-func-plan"), planDir)
	testCopyDir(t, testFixturePath("changed-file-func-apply"), applyDir)
	t.Chdir(planDir)

	p := planVarsFixtureProvider()
	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Body: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"foo": {Type: cty.String, Optional: true},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_instance": {
				Body: &configschema.Block{
					Attributes: map[string]*configschema.Attribute{
						"id": {Type: cty.String, Optional: true, Computed: true},
					},
				},
			},
		},
	}

	view, done := testView(t)
	c := &PlanCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			View:             view,
		},
	}

	args := []string{
		"-out", filepath.Join(applyDir, "planfile"),
	}
	code := c.Run(args)
	output := done(t)
	if code != 0 {
		t.Fatalf("non-zero exit %d\n\n%s", code, output.Stderr())
	}

	t.Chdir(applyDir)

	view, done = testView(t)
	apply := &ApplyCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(p),
			Ui:               new(cli.MockUi),
			View:             view,
		},
	}
	args = []string{
		"planfile",
	}
	code = apply.Run(args)
	output = done(t)
	if code != 0 {
		t.Fatalf("non-zero exit %d\n\n%s", code, output.Stderr())
	}
}
