package test

import (
	"reflect"
	"regexp"
	"strings"
	"testing"

	"github.com/hashicorp/terraform/addrs"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/terraform"
)

func TestResource_basic(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckNoResourceAttr(
						"test_resource.foo", "list.#",
					),
				),
			},
		},
	})
}

func TestResource_changedList(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckNoResourceAttr(
						"test_resource.foo", "list.#",
					),
				),
			},
			{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	list = ["a"]
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list.#", "1",
					),
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list.0", "a",
					),
				),
			},
			{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	list = ["a", "b"]
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list.#", "2",
					),
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list.0", "a",
					),
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list.1", "b",
					),
				),
			},
			{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	list = ["b"]
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list.#", "1",
					),
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list.0", "b",
					),
				),
			},
		},
	})
}

// Targeted test in TestContext2Apply_ignoreChangesCreate
func TestResource_ignoreChangesRequired(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
        required = "yep"
	required_map = {
	    key = "value"
	}
        lifecycle {
                ignore_changes = ["required"]
        }
}
                               `),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
		},
	})
}

func TestResource_ignoreChangesEmpty(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	optional_force_new = "one"
	lifecycle {
		ignore_changes = []
	}
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	optional_force_new = "two"
	lifecycle {
		ignore_changes = []
	}
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
		},
	})
}

func TestResource_ignoreChangesForceNew(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "yep"
	required_map = {
	    key = "value"
	}
	optional_force_new = "one"
	lifecycle {
		ignore_changes = ["optional_force_new"]
	}
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "yep"
	required_map = {
	    key = "value"
	}
	optional_force_new = "two"
	lifecycle {
		ignore_changes = ["optional_force_new"]
	}
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
		},
	})
}

// Covers specific scenario in #6005, handled by normalizing boolean strings in
// helper/schema
func TestResource_ignoreChangesForceNewBoolean(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required           = "yep"
  required_map = {
    key = "value"
  }
  optional_force_new = "one"
  optional_bool      = true
  lifecycle {
    ignore_changes = ["optional_force_new"]
  }
}
				`),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required           = "yep"
  required_map = {
    key = "value"
  }
  optional_force_new = "two"
  optional_bool      = true
  lifecycle {
    ignore_changes = ["optional_force_new"]
  }
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
		},
	})
}

func TestResource_ignoreChangesMap(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "yep"
	required_map = {
	  key = "value"
	}
	optional_computed_map = {
		foo = "bar"
	}
	lifecycle {
		ignore_changes = ["optional_computed_map"]
	}
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "yep"
	required_map = {
	  key = "value"
	}
	optional_computed_map = {
		foo = "bar"
		no  = "update"
	}
	lifecycle {
		ignore_changes = ["optional_computed_map"]
	}
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
		},
	})
}

func TestResource_ignoreChangesDependent(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	count = 2
	required = "yep"
	required_map = {
		key = "value"
	}

	optional_force_new = "one"
	lifecycle {
		ignore_changes = ["optional_force_new"]
	}
}
resource "test_resource" "bar" {
	count = 2
	required = "yep"
	required_map = {
		key = "value"
	}
	optional = "${element(test_resource.foo.*.id, count.index)}"
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	count = 2
	required = "yep"
	required_map = {
		key = "value"
	}

	optional_force_new = "two"
	lifecycle {
		ignore_changes = ["optional_force_new"]
	}
}
resource "test_resource" "bar" {
	count = 2
	required = "yep"
	required_map = {
		key = "value"
	}
	optional = "${element(test_resource.foo.*.id, count.index)}"
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
		},
	})
}

func TestResource_ignoreChangesStillReplaced(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "yep"
  required_map = {
    key = "value"
  }
  optional_force_new = "one"
  optional_bool      = true
  lifecycle {
    ignore_changes = ["optional_bool"]
  }
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "yep"
  required_map = {
    key = "value"
  }
  optional_force_new = "two"
  optional_bool      = false
  lifecycle {
    ignore_changes = ["optional_bool"]
  }
}
				`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
		},
	})
}

func TestResource_ignoreChangesCustomizeDiff(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "yep"
  required_map = {
    key = "value"
  }
  optional      = "a"
  lifecycle {
    ignore_changes = [optional]
  }
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "planned_computed", "a",
					),
				),
			},
			// On this step, `optional` changes, but `planned_computed`
			// should remain as "a" because we have set `ignore_changes`
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "yep"
  required_map = {
    key = "value"
  }
  optional      = "b"
  lifecycle {
    ignore_changes = [optional]
  }
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "planned_computed", "a",
					),
				),
			},
		},
	})
}

// Reproduces plan-time panic when the wrong type is interpolated in a list of
// maps.
// TODO: this should return a type error, rather than silently setting an empty
//       list
func TestResource_dataSourceListMapPanic(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required = "val"
  required_map = {x = "y"}
  list_of_map = "${var.maplist}"
}

variable "maplist" {
  type = "list"

  default = [
    {a = "b"}
  ]
}
				`),
				ExpectError: nil,
				Check: func(s *terraform.State) error {
					return nil
				},
			},
		},
	})
}

func TestResource_dataSourceIndexMapList(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required = "val"

  required_map = {
    x = "y"
  }

  list_of_map = [
    {
      a = "1"
      b = "2"
    },
    {
      c = "3"
      d = "4"
    },
  ]
}

output "map_from_list" {
  value = "${test_resource.foo.list_of_map[0]}"
}

output "value_from_map_from_list" {
  value = "${lookup(test_resource.foo.list_of_map[1], "d")}"
}
				`),
				ExpectError: nil,
				Check: func(s *terraform.State) error {
					root := s.ModuleByPath(addrs.RootModuleInstance)
					mapOut := root.Outputs["map_from_list"].Value
					expectedMapOut := map[string]interface{}{
						"a": "1",
						"b": "2",
					}

					valueOut := root.Outputs["value_from_map_from_list"].Value
					expectedValueOut := "4"

					if !reflect.DeepEqual(mapOut, expectedMapOut) {
						t.Fatalf("Expected: %#v\nGot: %#v", expectedMapOut, mapOut)
					}
					if !reflect.DeepEqual(valueOut, expectedValueOut) {
						t.Fatalf("Expected: %#v\nGot: %#v", valueOut, expectedValueOut)
					}
					return nil
				},
			},
		},
	})
}

func testAccCheckResourceDestroy(s *terraform.State) error {
	return nil
}

func TestResource_removeForceNew(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "yep"
	required_map = {
	  key = "value"
	}
	optional_force_new = "here"
}
				`),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "yep"
	required_map = {
	  key = "value"
	}
}
				`),
			},
		},
	})
}

func TestResource_unknownFuncInMap(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "ok"
	required_map = {
	  key = "${uuid()}"
	}
}
				`),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

// Verify that we can destroy when a managed resource references something with
// a count of 1.
func TestResource_countRefDestroyError(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: strings.TrimSpace(`
resource "test_resource" "one" {
	count = 1
	required     = "ok"
	required_map = {
	  key = "val"
	}
}

resource "test_resource" "two" {
	required     = test_resource.one[0].id
	required_map = {
	  key = "val"
	}
}
				`),
			},
		},
	})
}

func TestResource_emptyMapValue(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required     = "ok"
	required_map = {
		a = "a"
		b = ""
	}
}
				`),
			},
		},
	})
}

func TestResource_updateError(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "first"
  required_map = {
    a = "a"
  }
}
`),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "second"
  required_map = {
    a = "a"
  }
  apply_error = "update_error"
}
`),
				ExpectError: regexp.MustCompile("update_error"),
			},
		},
	})
}

func TestResource_applyError(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "second"
  required_map = {
    a = "a"
  }
  apply_error = "apply_error"
}
`),
				ExpectError: regexp.MustCompile("apply_error"),
			},
		},
	})
}

func TestResource_emptyStrings(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "second"
  required_map = {
    a = "a"
  }

  list = [""]
}
`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr("test_resource.foo", "list.0", ""),
				),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "second"
  required_map = {
    a = "a"
  }

  list = ["", "b"]
}
`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr("test_resource.foo", "list.0", ""),
					resource.TestCheckResourceAttr("test_resource.foo", "list.1", "b"),
				),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "second"
  required_map = {
    a = "a"
  }

  list = [""]
}
`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr("test_resource.foo", "list.0", ""),
				),
			},
		},
	})
}

func TestResource_setDrift(t *testing.T) {
	testProvider := testAccProviders["test"]
	res := testProvider.(*schema.Provider).ResourcesMap["test_resource"]

	// reset the Read function after the test
	defer func() {
		res.Read = testResourceRead
	}()

	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "first"
  required_map = {
    a = "a"
	}
	set = ["a", "b"]
}
`),
				Check: func(s *terraform.State) error {
					return nil
				},
			},
			resource.TestStep{
				PreConfig: func() {
					// update the Read function to return the wrong "set" attribute values.
					res.Read = func(d *schema.ResourceData, meta interface{}) error {
						// update as expected first
						if err := testResourceRead(d, meta); err != nil {
							return err
						}
						d.Set("set", []interface{}{"a", "x"})
						return nil
					}
				},
				// Leave the config, so we can detect the mismatched set values.
				// Updating the config would force the test to pass even if the Read
				// function values were ignored.
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
  required     = "second"
  required_map = {
    a = "a"
	}
	set = ["a", "b"]
}
`),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func TestResource_optionalComputedMap(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "yep"
	required_map = {
	  key = "value"
	}
	optional_computed_map = {
		foo = "bar"
		baz = ""
	}
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "optional_computed_map.foo", "bar",
					),
					resource.TestCheckResourceAttr(
						"test_resource.foo", "optional_computed_map.baz", "",
					),
				),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required           = "yep"
	required_map = {
	  key = "value"
	}
	optional_computed_map = {}
}
				`),
				// removing the map from the config should still leave an empty computed map
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "optional_computed_map.%", "0",
					),
				),
			},
		},
	})
}

func TestResource_plannedComputed(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "ok"
	required_map = {
	  key = "value"
	}
	optional = "hi"
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "planned_computed", "hi",
					),
				),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "ok"
	required_map = {
	  key = "value"
	}
	optional = "changed"
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "planned_computed", "changed",
					),
				),
			},
		},
	})
}

func TestDiffApply_map(t *testing.T) {
	resSchema := map[string]*schema.Schema{
		"map": {
			Type:     schema.TypeMap,
			Optional: true,
			Computed: true,
			Elem:     &schema.Schema{Type: schema.TypeString},
		},
	}

	priorAttrs := map[string]string{
		"id":      "ok",
		"map.%":   "2",
		"map.foo": "bar",
		"map.bar": "",
	}

	diff := &terraform.InstanceDiff{
		Attributes: map[string]*terraform.ResourceAttrDiff{
			"map.foo": &terraform.ResourceAttrDiff{Old: "bar", New: "", NewRemoved: true},
			"map.bar": &terraform.ResourceAttrDiff{Old: "", New: "", NewRemoved: true},
		},
	}

	newAttrs, err := diff.Apply(priorAttrs, (&schema.Resource{Schema: resSchema}).CoreConfigSchema())
	if err != nil {
		t.Fatal(err)
	}

	expect := map[string]string{
		"id":    "ok",
		"map.%": "0",
	}

	if !reflect.DeepEqual(newAttrs, expect) {
		t.Fatalf("expected:%#v got:%#v", expect, newAttrs)
	}
}

func TestResource_dependsComputed(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
variable "change" {
	default = false
}

resource "test_resource" "foo" {
	required = "ok"
	required_map = {
	    key = "value"
	}
	optional = var.change ? "after" : ""
}

resource "test_resource" "bar" {
	count = var.change ? 1 : 0
	required = test_resource.foo.planned_computed
	required_map = {
		key = "value"
	}
}
				`),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
variable "change" {
	default = true
}

resource "test_resource" "foo" {
	required = "ok"
	required_map = {
	    key = "value"
	}
	optional = var.change ? "after" : ""
}

resource "test_resource" "bar" {
	count = var.change ? 1 : 0
	required = test_resource.foo.planned_computed
	required_map = {
		key = "value"
	}
}
				`),
			},
		},
	})
}

func TestResource_optionalComputedBool(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
}
				`),
			},
		},
	})
}

func TestResource_replacedOptionalComputed(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource_nested" "a" {
}

resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	optional_computed = test_resource_nested.a.id
}
				`),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource_nested" "b" {
}

resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	optional_computed = test_resource_nested.b.id
}
				`),
			},
		},
	})
}

func TestResource_floatInIntAttr(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	int = 40.2
}
				`),
				ExpectError: regexp.MustCompile(`must be a whole number, got 40.2`),
			},
		},
	})
}

func TestResource_unsetNil(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	optional = "a"
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr("test_resource.foo", "optional", "a"),
				),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr("test_resource.foo", "optional", ""),
				),
			},
		},
	})
}

// Verify we can use use numeric indices in `ignore_changes` paths.
func TestResource_ignoreChangesIndex(t *testing.T) {
	resource.UnitTest(t, resource.TestCase{
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckResourceDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	list_of_map = [
		{
			a = "b"
		}
	]

	lifecycle {
		ignore_changes = [list_of_map[0]["a"]]
	}
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list_of_map.0.a", "b",
					),
				),
			},
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	list_of_map = [
		{
			a = "c"
		}
	]

	lifecycle {
		ignore_changes = [list_of_map[0]["a"]]
	}
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list_of_map.0.a", "b",
					),
				),
			},
			// set ignore_changes to a prefix of the changed value
			resource.TestStep{
				Config: strings.TrimSpace(`
resource "test_resource" "foo" {
	required = "yep"
	required_map = {
	    key = "value"
	}
	list_of_map = [
		{
			a = "d"
		}
	]

	lifecycle {
		ignore_changes = [list_of_map[0]]
	}
}
				`),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(
						"test_resource.foo", "list_of_map.0.a", "b",
					),
				),
			},
		},
	})
}
