Facing Issue while testing data sources

Hi Team ,
we are using terraform plugin framework(GitHub - hashicorp/terraform-plugin-framework: A next-generation framework for building Terraform providers.) to develop resources and data sources for our use case . For testing , we 're using github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource

I wanted to test my datasource where one attribute(let say id) is mandatorily needed from the user to fetch the data for that particular id .
There might be a situation where user doesn’t provide that id at all. So I wrote a test case for that where the config says as below :

data “abc_sp” “test21” {
names = [“pool1”]
}
Important Point to note is that I’m not providing the id in the above config. Just providing the name. So here while writing the acceptance test , I’m expecting an error "Error: Invalid Attribute Combination ".

{
Config: ProviderConfigForTesting + SPDataSourceWithoutID,
ExpectError: regexp.MustCompile(.*Error: Invalid Attribute Combination*.),
},
When I run this test case , I’m getting an error as below :
Error running post-test destroy, there may be dangling resources: exit status 1

    Error: Invalid Attribute Combination
    
      with data.abc_sp.test21,
      on terraform_plugin_test.tf line 8, in data "abc_sp" "test21":
       8: data "abc_sp" "test21" {
    
    No attribute specified when one (and only one) of [id] is
    required

FAIL
FAIL terraform-provider/abc 20.084s
FAIL

I’m expecting this to pass as I’ve provided the correct error message in the Expect error section. But It says Error running post-test destroy, there may be dangling resources: exit status 1. May I know why is this happening??

Hi @AnikaAgiwal2711 :wave:

I’m using the following to try and reproduce the issue you’re describing:

data source

func (e *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"id": schema.StringAttribute{
				MarkdownDescription: "Example identifier",
				Required:            true,
			},
		},
	}
}

type exampleDataSourceData struct {
	Id types.String `tfsdk:"id"`
}

func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
	var data exampleDataSourceData

	diags := req.Config.Get(ctx, &data)
	resp.Diagnostics.Append(diags...)

	if resp.Diagnostics.HasError() {
		return
	}

	tflog.Trace(ctx, "read a data source")

	diags = resp.State.Set(ctx, &data)
	resp.Diagnostics.Append(diags...)
}

Configuration

data "example_datasource" "example" {
}

Test

func TestAccExampleDataSourceExpectError(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
		Steps: []resource.TestStep{
			{
				Config:      testAccExampleDataSourceConfig,
				ExpectError: regexp.MustCompile("Missing required argument"),
			},
		},
	})
}

Using the code listed the test passes as the CLI output from Terraform contains “Missing required argument”.

Running terraform apply with the code listed results in the following CLI output:

│ Error: Missing required argument
│ 
│   on data-source.tf line 9, in data "example_datasource" "example":
│    9: data "example_datasource" "example" {
│ 
│ The argument "id" is required, but no definition was found.

Could you perhaps supply more information on the code you’re using within your data source and the configuration you’re using? Might also be useful to see the test code too.

Possibly worth checking which version of the Framework that you’re using as well.

Hi @bendbennett :wave:

Please check the below information

DataSource

func (e *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
“id”: schema.StringAttribute{
Description: “ID.”,
MarkdownDescription: “ID.”,
Optional: true,
},
“name”: schema.StringAttribute{
Description: “Name.”,
MarkdownDescription: “Name.”,
Optional: true,
Validators: validator.String{
stringvalidator.ExactlyOneOf(path.MatchRoot(“id”)),
},
},
“sp_ids”: schema.ListAttribute{
Description: “SP ID.”,
MarkdownDescription: “SP ID.”,
ElementType: types.StringType,
Optional: true,
},
“sp_names”: schema.ListAttribute{
Description: “SP names.”,
MarkdownDescription: “SP names.”,
ElementType: types.StringType,
Optional: true,
Validators: validator.List{
listvalidator.ConflictsWith(path.MatchRoot(“sp_ids”)),
},
},
},
}
}

type exampleDataSourceData struct {
Id types.String tfsdk:"id"
Name types.String tfsdk:"name"
SPId types.List tfsdk:"sp_ids"
SPName types.List tfsdk:"sp_names"

}

func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var state exampleDataSourceData

diags := req.Config.Get(ctx, &state)

resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
	return
}

//Some reading logic

tflog.Trace(ctx, "read a data source")

diags = resp.State.Set(ctx, &data)
resp.Diagnostics.Append(diags...)

}

Configuration - here I’m trying to test a scenario without id/name (Just mentioning sp_names)
If you analyze my Schema, User needs to provide id/name

data “example_datasource” “example” {
sp_names = [“demo”]
}

Test

func TestAccExampleDataSourceExpectError(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: resource.TestStep{
{
Config: testAccExampleDataSourceConfig,
ExpectError: regexp.MustCompile(“Error: Invalid Attribute Combination”),
},
},
})
}

Result which I’m getting after running the acceptance test

Error running post-test destroy, there may be dangling resources: exit status 1

    Error: Invalid Attribute Combination
    
      with data.example_datasource.example,
      on terraform_plugin_test.tf line 8, in data "example_datasource" "example21":
       8: data "example_datasource" "example" {
    
    No attribute specified when one (and only one) of [id] is
    required

version for the plugin framework is : v1.0.1

Hi @AnikaAgiwal2711,

I’ve used the following code and configuration, which is as you supplied with some minor changes.

data source

func (e *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"id": schema.StringAttribute{
				Description:         "ID.",
				MarkdownDescription: "ID.",
				Optional:            true,
			},
			"name": schema.StringAttribute{
				Description:         "Name.",
				MarkdownDescription: "Name.",
				Optional:            true,
				Validators: []validator.String{
					stringvalidator.ExactlyOneOf(path.MatchRoot("id")),
				},
			},
			"sp_ids": schema.ListAttribute{
				Description:         "SP ID.",
				MarkdownDescription: "SP ID.",
				ElementType:         types.StringType,
				Optional:            true,
			},
			"sp_names": schema.ListAttribute{
				Description:         "SP names.",
				MarkdownDescription: "SP names.",
				ElementType:         types.StringType,
				Optional:            true,
				Validators: []validator.List{
					listvalidator.ConflictsWith(path.MatchRoot("sp_ids")),
				},
			},
		},
	}
}

type exampleDataSourceData struct {
	Id     types.String `tfsdk:"id"`
	Name   types.String `tfsdk:"name"`
	SPId   types.List   `tfsdk:"sp_ids"`
	SPName types.List   `tfsdk:"sp_names"`
}

func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
	var state exampleDataSourceData

	diags := req.Config.Get(ctx, &state)

	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}

	//Some reading logic

	tflog.Trace(ctx, "read a data source")

	diags = resp.State.Set(ctx, &state)
	resp.Diagnostics.Append(diags...)
}

configuration

data "example_datasource" "example" {
  sp_names = ["demo"]
}

test

func TestAccExampleDataSourceExpectError(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
		Steps: []resource.TestStep{
			{
				Config:      testAccExampleDataSourceConfig,
				ExpectError: regexp.MustCompile("Invalid Attribute Combination"),
			},
		},
	})
}

const testAccExampleDataSourceConfig = `
data "example_datasource" "test" {
  sp_names = ["demo"]
}

When running the test I see:

=== RUN   TestAccExampleDataSourceExpectError
--- PASS: TestAccExampleDataSourceExpectError (0.20s)
PASS

I do not see any message indicating Error running post-test destroy, there may be dangling resources: exit status 1.

Running terraform apply I see:

│ Error: Invalid Attribute Combination
│ 
│   with data.example_datasource.example,
│   on data-source.tf line 9, in data "example_datasource" "example":
│    9: data "example_datasource" "example" {
│ 
│ No attribute specified when one (and only one) of [id] is required

Perhaps there is something else in your code which is giving rise to the “dangling resource” error?

Thanks @bendbennett for analyzing this.
After your reply, I tried to analyze what is giving rise to this “dangling resource” error.

My findings are as follows:

when I run the below test case alone (with no positive test cases to run), then it passes and doesn’t show this dangling resource error.

func TestAccExampleDataSourceExpectError(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
		Steps: []resource.TestStep{
			{
				Config:      testAccExampleDataSourceConfig,
				ExpectError: regexp.MustCompile("Invalid Attribute Combination"),
			},
		},
	})
}

const testAccExampleDataSourceConfig = `
data "example_datasource" "test" {
  sp_names = ["demo"]
}

But now when I try to run this test case along with a positive test case (where I don’t expect any error), This gives rise to the dangling resource error which I’ve posted earlier.

func TestAccExampleDataSourceExpectError(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
		Steps: []resource.TestStep{
			{
				Config:      testAccExampleDataSourceConfig,
				ExpectError: regexp.MustCompile("Invalid Attribute Combination"),
			},
	               {
				Config: ProviderConfigForTesting + SPDataSourceConfig1,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr("data.example_datasource.example2", "sp_names.0", "demo"),
					resource.TestCheckResourceAttr("data.example_datasource.example2", "id", "abcde"),
				),
			},
		},
	})
}

const testAccExampleDataSourceConfig = `
data "example_datasource" "test" {
  sp_names = ["demo"]
}

var SPDataSourceConfig1 = `
data "example_datasource" "example2" {
	id = "abcde"
	sp_names = ["demo"]
}
`

Can you verify this once on your side. Because I just want to find out the reason why is this thing happening ?

Hi @AnikaAgiwal2711 :wave:

Can you let me see what is in ProviderConfigForTesting and SPDataSourceConfig1?

Hi @bendbennett :wave:

ProviderConfigForTesting has the provider configuration which has the host endpoint, and user credentials which contains username and password for that endpoint. Apologies, I can’t provide the values as it is bit confidential. But it is something as below.
provider “example” {
username = “”
password = “”
endpoint = “”
insecure = true
}

SPDataSourceConfig1 contains both id and sp_names which makes it a positive test case.

var SPDataSourceConfig1 = data "example_datasource" "example2" { id = "abcde" sp_names = ["demo"] }

Hi @AnikaAgiwal2711

The configuration you’ve specified looks a little bit unusual.

Do you have a variable declaration (i.e., var SPDataSourceConfig1 = data "example_datasource".....) as part of the configuration?

What is the outcome if you specify the configuration as follows:

const testAccExampleDataSourceConfigWithProvider = `
provider "example" {
  username = ""
  password = ""
  endpoint = ""
  insecure = true
}

data "example_datasource" "example2" { 
  id = "abcde" 
  sp_names = ["demo"] 
}
`

It might be worth having a scan through input variables for examples of their usage within configuration.

I’ve tried the above approach and found that there is no change in the outcome even if I specify the configuration mentioned above. Still, I face the issue.

Following runs successfully:

func TestAccExampleDataSourceExpectError(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
		Steps: []resource.TestStep{
			{
				Config:      testAccExampleDataSourceConfig,
				ExpectError: regexp.MustCompile("Invalid Attribute Combination"),
			},
			{
				Config: testAccExampleDataSourceConfig2,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr("data.example_datasource.example2", "sp_names.0", "demo"),
					resource.TestCheckResourceAttr("data.example_datasource.example2", "id", "abcde"),
				)},
		},
	})
}

const testAccExampleDataSourceConfig = `
data "example_datasource" "test" {
  sp_names = ["demo"]
}
`

const testAccExampleDataSourceConfig2 = `
data "example_datasource" "example2" {
  id = "abcde"
  sp_names = ["demo"]
}
`