Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changes/unreleased/BUG FIXES-20260211-103028.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'querycheck: Fix behaviour of `ExpectLength` querycheck to match on the provided resource address before comparing the count'
time: 2026-02-11T10:30:28.750109+01:00
custom:
Issue: "604"
5 changes: 5 additions & 0 deletions .changes/unreleased/BUG FIXES-20260211-122825.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'querycheck: Fix behaviour of `ExpectLengthAtLeast` querycheck to match on the provided resource address before comparing the count'
time: 2026-02-11T12:28:25.372146+01:00
custom:
Issue: "607"
5 changes: 5 additions & 0 deletions .changes/unreleased/FEATURES-20260211-122845.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: FEATURES
body: 'querycheck: Add new `ExpectLengthForMultiple` querycheck for validating the number of found resources for multiple list blocks'
Copy link
Member

Choose a reason for hiding this comment

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

To match the new name 🙂

Suggested change
body: 'querycheck: Add new `ExpectLengthForMultiple` querycheck for validating the number of found resources for multiple list blocks'
body: 'querycheck: Add new `ExpectTotalLengthForMatching` querycheck for validating the number of found resources for multiple list blocks'

time: 2026-02-11T12:28:45.926523+01:00
custom:
Issue: "607"
25 changes: 21 additions & 4 deletions querycheck/expect_result_length_atleast.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package querycheck
import (
"context"
"fmt"
"strings"
)

var _ QueryResultCheck = expectLengthAtLeast{}
Expand All @@ -17,15 +18,31 @@ type expectLengthAtLeast struct {

// CheckQuery implements the query check logic.
func (e expectLengthAtLeast) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) {
if req.QuerySummary == nil {
if req.QuerySummary == nil && len(req.QuerySummaries) == 0 {
resp.Error = fmt.Errorf("no completed query information available")
return
}

if req.QuerySummary.Total < e.check {
resp.Error = fmt.Errorf("Query result of at least length %v - expected but got %v.", e.check, req.QuerySummary.Total)
return
for _, summary := range req.QuerySummaries {
address := summary.Address

// this brings the behaviour of this check in-line with the other query checks where the resource
// address needs to be provided without the `list.` prefix, but maintains the previous behaviour
// to not break existing tests that may be using the `list.` prefix in the resource address
if !strings.HasPrefix(e.resourceAddress, "list.") {
address = strings.TrimPrefix(summary.Address, "list.")
}

if strings.EqualFold(address, e.resourceAddress) {
if summary.Total < e.check {
resp.Error = fmt.Errorf("Query result of at least length %v - expected but got %v.", e.check, summary.Total)
return
}
return
}
}

resp.Error = fmt.Errorf("the list block %s was not found in the query results", e.resourceAddress)
}

// ExpectLengthAtLeast returns a query check that asserts that the length of the query result is at least the given value.
Expand Down
110 changes: 103 additions & 7 deletions querycheck/expect_result_length_atleast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,59 @@ func TestResultLengthAtLeast(t *testing.T) {
resource_group_name = "foo"
}
}
list "examplecloud_containerette" "test2" {
`,
QueryResultChecks: []querycheck.QueryResultCheck{
querycheck.ExpectLengthAtLeast("examplecloud_containerette.test", 2),
},
},
},
})
}

func TestResultLengthAtLeast_Multiple(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": examplecloudListResource(),
"examplecloud_bananette": examplecloudListResource(),
},
Resources: map[string]testprovider.Resource{
"examplecloud_containerette": examplecloudResource(),
"examplecloud_bananette": examplecloudResourceBananette(),
},
}),
},
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: true,
Config: `
provider "examplecloud" {}
list "examplecloud_containerette" "test" {
provider = examplecloud

config {
resource_group_name = "foo"
}
}
list "examplecloud_bananette" "test" {
provider = examplecloud

config {
Expand All @@ -65,8 +117,8 @@ func TestResultLengthAtLeast(t *testing.T) {
}
`,
QueryResultChecks: []querycheck.QueryResultCheck{
querycheck.ExpectLengthAtLeast("examplecloud_containerette.test", 2),
querycheck.ExpectLengthAtLeast("examplecloud_containerette.test2", 1),
querycheck.ExpectLengthAtLeast("examplecloud_containerette.test", 6),
querycheck.ExpectLengthAtLeast("examplecloud_bananette.test", 2),
},
},
},
Expand Down Expand Up @@ -114,18 +166,62 @@ func TestResultLengthAtLeast_TooFewResults(t *testing.T) {
resource_group_name = "foo"
}
}
list "examplecloud_containerette" "test2" {
`,
QueryResultChecks: []querycheck.QueryResultCheck{
querycheck.ExpectLengthAtLeast("examplecloud_containerette.test", 8),
},
ExpectError: regexp.MustCompile("Query result of at least length 8 - expected but got 6."),
},
},
})
}

func TestResultLengthAtLeast_WrongResourceAddress(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": examplecloudListResource(),
},
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: true,
Config: `
provider "examplecloud" {}
list "examplecloud_containerette" "test" {
provider = examplecloud

config {
resource_group_name = "bar"
resource_group_name = "foo"
}
}
`,
QueryResultChecks: []querycheck.QueryResultCheck{
querycheck.ExpectLengthAtLeast("examplecloud_containerette.test", 8),
querycheck.ExpectLengthAtLeast("examplecloud_containerette.test2", 8),
},
ExpectError: regexp.MustCompile("Query result of at least length 8 - expected but got 6."),
ExpectError: regexp.MustCompile("the list block examplecloud_containerette.test2 was not found in the query results"),
},
},
})
Expand Down
54 changes: 54 additions & 0 deletions querycheck/expect_total_result_length_for_matching.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: MPL-2.0

package querycheck

import (
"context"
"fmt"
"regexp"
)

var _ QueryResultCheck = expectTotalLengthForMatching{}

type expectTotalLengthForMatching struct {
regex *regexp.Regexp
check int
}

// CheckQuery implements the query check logic.
func (e expectTotalLengthForMatching) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) {
if req.QuerySummary == nil && len(req.QuerySummaries) == 0 {
resp.Error = fmt.Errorf("no query summary information available")
return
}

total := 0
matchFound := false
for _, summary := range req.QuerySummaries {
if e.regex.MatchString(summary.Address) {
total += summary.Total
matchFound = true
}
}

if !matchFound {
resp.Error = fmt.Errorf("no list resources matching the provided regex pattern %s were found in the query results", e.regex.String())
return
}

if total != e.check {
resp.Error = fmt.Errorf("expected total of found resources to be %d, got %d", e.check, total)
}
}

// ExpectTotalLengthForMatching returns a query check that asserts that the sum of query result lengths
// produced by multiple list blocks is exactly the given value.
//
// This query check can only be used with managed resources that support query. Query is only supported in Terraform v1.14+
func ExpectTotalLengthForMatching(regex *regexp.Regexp, length int) QueryResultCheck {
return expectTotalLengthForMatching{
regex: regex,
check: length,
}
}
Loading
Loading