Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions helper/resource/query/examplecloud_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package query_test
import (
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/list"
"github.com/hashicorp/terraform-plugin-testing/internal/teststep"
Expand Down
7 changes: 7 additions & 0 deletions helper/resource/query/query_checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ func RunQueryChecks(ctx context.Context, t testing.T, query []tfjson.LogMsg, que

for _, msg := range query {
switch v := msg.(type) {
case tfjson.DiagnosticLogMessage:
if v.Diagnostic.Severity == tfjson.DiagnosticSeverityError {
// TODO reimplement missing .tf config error
result = append(result, fmt.Errorf("query error diagnostic - summary: %s, detail: %s", v.Diagnostic.Summary, v.Diagnostic.Detail))
continue
}

case tfjson.ListResourceFoundMessage:
found = append(found, v.ListResourceFound)
case tfjson.ListCompleteMessage:
Expand Down
89 changes: 89 additions & 0 deletions helper/resource/query/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
package query_test

import (
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"

r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/list"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver"
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
"github.com/hashicorp/terraform-plugin-testing/querycheck"
Expand Down Expand Up @@ -100,3 +104,88 @@ func TestQuery(t *testing.T) {
},
})
}

func TestQuery_ExpectError_ValidationError(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_14_0),
},
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"examplecloud": providerserver.NewProviderServer(testprovider.Provider{
ListResources: map[string]testprovider.ListResource{
"examplecloud_containerette": {
IncludeResource: true,
SchemaResponse: &list.SchemaResponse{
Schema: &tfprotov6.Schema{
Block: &tfprotov6.SchemaBlock{
Attributes: []*tfprotov6.SchemaAttribute{
{
Name: "resource_group_name",
Type: tftypes.String,
Required: true,
},
},
},
},
},
ValidateListConfigResponse: &list.ValidateListConfigResponse{
Diagnostics: []*tfprotov6.Diagnostic{
{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Diagnostic summary",
Detail: "Diagnostic details",
},
},
},
},
},
Resources: map[string]testprovider.Resource{
"examplecloud_containerette": examplecloudResource(),
},
}),
},
Steps: []r.TestStep{
{ // config mode step 1 needs tf file with terraform providers block
// this step should provision all the resources that the query is support to list
// for simplicity we're only "provisioning" one here
Config: `
resource "examplecloud_containerette" "primary" {
name = "banana"
resource_group_name = "foo"
location = "westeurope"

instances = 5
}`,
},
{ // Query mode step 2, operates on .tfquery.hcl files (needs tf file with terraform providers block)
// ```provider "examplecloud" {}``` has a slightly different syntax for a .tfquery.hcl file
// provider bock simulates a real providers workflow
// "config" in this case means configuration of the list resource/filters

Query: true,
Config: `
provider "examplecloud" {}

list "examplecloud_containerette" "test" {
provider = examplecloud

config {
resource_group_name = "foo"
}
}

list "examplecloud_containerette" "test2" {
provider = examplecloud

config {
resource_group_name = "bar"
}
}
`,
ExpectError: regexp.MustCompile(`Diagnostic summary`),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something worth noting that might not be an immediate problem, but more of a potential future confusion for folks familiar with ExpectError

Since Query is using the machine readable output, the error message it is running the regex on with that mode is the formatted %+v of the diagnostics, which is different then other modes that use the formatted Terraform CLI output of the diagnostics. That can make the output hard to read / different to match than the other modes:

--- FAIL: TestQuery_ExpectError_ValidationError (0.49s)
    /Users/austin.valle/code/terraform-plugin-testing/helper/resource/query/query_test.go:111: Step 2/2 error running query, expected an error with pattern (Not the correct diagnostic summary), no match on: running terraform query command returned diagnostics: [{baseLogMessage:{Lvl:error Msg:Error: Diagnostic summary Time:2025-10-29 09:10:02.151561 -0400 EDT} Diagnostic:{Severity:error Summary:Diagnostic summary Detail:Diagnostic details Range:0x140004547c0 Snippet:0x14000030640}} {baseLogMessage:{Lvl:error Msg:Error: Diagnostic summary Time:2025-10-29 09:10:02.151735 -0400 EDT} Diagnostic:{Severity:error Summary:Diagnostic summary Detail:Diagnostic details Range:0x14000454900 Snippet:0x14000030690}}]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good point. Maybe we should introduce a string formatter for machine readable error messaging in a follow-up PR.

},
},
})
}
5 changes: 2 additions & 3 deletions helper/resource/testing_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-version"
tfjson "github.com/hashicorp/terraform-json"
"github.com/hashicorp/terraform-plugin-testing/helper/resource/query"
"github.com/mitchellh/go-testing-interface"

"github.com/hashicorp/terraform-plugin-testing/helper/resource/query"

"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/internal/logging"
"github.com/hashicorp/terraform-plugin-testing/internal/plugintest"
Expand Down Expand Up @@ -384,7 +385,6 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest
return err
})
if err != nil {
fmt.Printf("Step %d/%d Query Output:\n%s\n", stepNumber, len(c.Steps), queryOut)
t.Fatalf("Step %d/%d error running query: %s", stepNumber, len(c.Steps), err)
}

Expand Down Expand Up @@ -414,7 +414,6 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest
)
t.Fatalf("Step %d/%d error running query checks: %s", stepNumber, len(c.Steps), err)
}
fmt.Printf("Step %d/%d Query Output:\n%s\n", stepNumber, len(c.Steps), queryOut)
}

logging.HelperResourceDebug(ctx, "Finished TestStep")
Expand Down
15 changes: 3 additions & 12 deletions internal/plugintest/working_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ package plugintest
import (
"context"
"fmt"
"github.com/hashicorp/terraform-exec/tfexec"
tfjson "github.com/hashicorp/terraform-json"
"io"
"os"
"path/filepath"

"github.com/hashicorp/terraform-exec/tfexec"
tfjson "github.com/hashicorp/terraform-json"

"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/internal/logging"
"github.com/hashicorp/terraform-plugin-testing/internal/teststep"
Expand Down Expand Up @@ -525,7 +526,6 @@ func (wd *WorkingDir) Schemas(ctx context.Context) (*tfjson.ProviderSchemas, err

func (wd *WorkingDir) Query(ctx context.Context) ([]tfjson.LogMsg, error) {
var messages []tfjson.LogMsg
var diags []tfjson.LogMsg

logging.HelperResourceTrace(ctx, "Calling Terraform CLI providers query command")

Expand All @@ -546,18 +546,9 @@ func (wd *WorkingDir) Query(ctx context.Context) ([]tfjson.LogMsg, error) {
return nil, fmt.Errorf("retrieving message: %w", msg.Err)
}

if msg.Msg.Level() == tfjson.Error {
// TODO reimplement missing .tf config error
diags = append(diags, msg.Msg)
continue
}
messages = append(messages, msg.Msg)
}

if len(diags) > 0 {
return nil, fmt.Errorf("running terraform query command returned diagnostics: %+v", diags)
}

logging.HelperResourceTrace(ctx, "Called Terraform CLI providers query command")

return messages, nil
Expand Down
6 changes: 6 additions & 0 deletions internal/testing/testprovider/list_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ type ListResource struct {
ValidateListConfigResponse *list.ValidateListConfigResponse
}

func (r ListResource) ValidateListConfig(ctx context.Context, req list.ValidateListConfigRequest, resp *list.ValidateListConfigResponse) {
if r.ValidateListConfigResponse != nil {
resp.Diagnostics = r.ValidateListConfigResponse.Diagnostics
}
}

func (r ListResource) Schema(ctx context.Context, req list.SchemaRequest, resp *list.SchemaResponse) {
if r.SchemaResponse != nil {
resp.Diagnostics = r.SchemaResponse.Diagnostics
Expand Down
6 changes: 6 additions & 0 deletions internal/testing/testsdk/list/list_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
type ListResource interface {
Schema(context.Context, SchemaRequest, *SchemaResponse)
List(context.Context, ListRequest, *ListResultsStream)
ValidateListConfig(context.Context, ValidateListConfigRequest, *ValidateListConfigResponse)
}

type ListRequest struct {
Expand Down Expand Up @@ -45,7 +46,12 @@ type ListResult struct {
Diagnostics []*tfprotov6.Diagnostic
}

type ValidateListConfigRequest struct {
Config tftypes.Value
}

type ValidateListConfigResponse struct {
Diagnostics []*tfprotov6.Diagnostic
}

type SchemaRequest struct{}
Expand Down
51 changes: 50 additions & 1 deletion internal/testing/testsdk/providerserver/providerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,56 @@ func (s ProviderServer) ListResource(ctx context.Context, req *tfprotov6.ListRes
}

func (s ProviderServer) ValidateListResourceConfig(ctx context.Context, req *tfprotov6.ValidateListResourceConfigRequest) (*tfprotov6.ValidateListResourceConfigResponse, error) {
return &tfprotov6.ValidateListResourceConfigResponse{}, nil
// Copy over identity if it's supported
identitySchemaReq := resource.IdentitySchemaRequest{}
identitySchemaResp := &resource.IdentitySchemaResponse{}

r, err := ProviderResource(s.Provider, req.TypeName)
if err != nil {
return nil, fmt.Errorf("failed to retrieve resource: %v", err)
}
r.IdentitySchema(ctx, identitySchemaReq, identitySchemaResp)
if len(identitySchemaResp.Diagnostics) > 0 {
return nil, fmt.Errorf("failed to retrieve resource schema: %v", identitySchemaResp.Diagnostics)
}

listresource, diag := ProviderListResource(s.Provider, req.TypeName)
if diag != nil {
return nil, fmt.Errorf("failed to retrieve resource identity schema: %v", err)
}

configSchemaReq := list.SchemaRequest{}
configSchemaResp := &list.SchemaResponse{}

listresource.Schema(ctx, configSchemaReq, configSchemaResp)
if len(configSchemaResp.Diagnostics) > 0 {
return nil, fmt.Errorf("failed to retrieve resource schema: %v", configSchemaResp.Diagnostics)
}

resourceSchemaResp := &resource.SchemaResponse{}
r.Schema(ctx, resource.SchemaRequest{}, resourceSchemaResp)
if resourceSchemaResp.Schema == nil {
return nil, fmt.Errorf("failed to retrieve resource schema: %v", resourceSchemaResp.Schema)
}

var config tftypes.Value
config, diag = DynamicValueToValue(configSchemaResp.Schema, req.Config)
if diag != nil {
return nil, fmt.Errorf("failed to convert config to value: %v", err)
}

validateReq := list.ValidateListConfigRequest{
Config: config,
}
validateResp := &list.ValidateListConfigResponse{}

listresource.ValidateListConfig(ctx, validateReq, validateResp)

resp := &tfprotov6.ValidateListResourceConfigResponse{
Diagnostics: validateResp.Diagnostics,
}

return resp, nil
}

func processListResults(req list.ListRequest, stream iter.Seq[list.ListResult]) iter.Seq[tfprotov6.ListResourceResult] {
Expand Down