Skip to content

Commit b0b505e

Browse files
authored
plancheck: Include known value in error message when asserting an unknown value (#456)
* plancheck: Include known value in error message when asserting an unknown value * add tests for invalid paths * add changelogs
1 parent 41b2d19 commit b0b505e

8 files changed

+528
-20
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: 'plancheck: Fixed bug with all unknown value plan checks where a valid path would return a "path not found" error.'
3+
time: 2025-03-17T19:05:58.513949-04:00
4+
custom:
5+
Issue: "450"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: ENHANCEMENTS
2+
body: 'plancheck: Improved the unknown value plan check error messages to include a known value if one exists.'
3+
time: 2025-03-17T19:06:34.946925-04:00
4+
custom:
5+
Issue: "450"

plancheck/expect_unknown_output_value.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,15 @@ func (e expectUnknownOutputValue) CheckPlan(ctx context.Context, req CheckPlanRe
3737
}
3838

3939
result, err := tfjsonpath.Traverse(change.AfterUnknown, tfjsonpath.Path{})
40-
4140
if err != nil {
42-
resp.Error = err
41+
// If we find the output in the known values, return a more explicit message
42+
knownVal, knownErr := tfjsonpath.Traverse(change.After, tfjsonpath.Path{})
43+
if knownErr == nil {
44+
resp.Error = fmt.Errorf("Expected unknown value at output %q, but found known value: \"%v\"", e.outputAddress, knownVal)
45+
return
46+
}
4347

48+
resp.Error = err
4449
return
4550
}
4651

@@ -53,7 +58,13 @@ func (e expectUnknownOutputValue) CheckPlan(ctx context.Context, req CheckPlanRe
5358
}
5459

5560
if !isUnknown {
56-
resp.Error = fmt.Errorf("attribute at path is known")
61+
// The output should have a known value, look first to return a more explicit message
62+
knownVal, knownErr := tfjsonpath.Traverse(change.After, tfjsonpath.Path{})
63+
if knownErr == nil {
64+
resp.Error = fmt.Errorf("Expected unknown value at output %q, but found known value: \"%v\"", e.outputAddress, knownVal)
65+
return
66+
}
67+
resp.Error = fmt.Errorf("Expected unknown value at output %q, but found known value", e.outputAddress)
5768

5869
return
5970
}

plancheck/expect_unknown_output_value_at_path.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,14 @@ func (e expectUnknownOutputValueAtPath) CheckPlan(ctx context.Context, req Check
3838
}
3939

4040
result, err := tfjsonpath.Traverse(change.AfterUnknown, e.valuePath)
41-
4241
if err != nil {
42+
// If we find the output in the known values, return a more explicit message
43+
knownVal, knownErr := tfjsonpath.Traverse(change.After, e.valuePath)
44+
if knownErr == nil {
45+
resp.Error = fmt.Errorf("Expected unknown value at output %q path %q, but found known value: \"%v\"", e.outputAddress, e.valuePath.String(), knownVal)
46+
return
47+
}
48+
4349
resp.Error = err
4450

4551
return
@@ -54,7 +60,13 @@ func (e expectUnknownOutputValueAtPath) CheckPlan(ctx context.Context, req Check
5460
}
5561

5662
if !isUnknown {
57-
resp.Error = fmt.Errorf("attribute at path is known")
63+
// The output should have a known value, look first to return a more explicit message
64+
knownVal, knownErr := tfjsonpath.Traverse(change.After, e.valuePath)
65+
if knownErr == nil {
66+
resp.Error = fmt.Errorf("Expected unknown value at output %q path %q, but found known value: \"%v\"", e.outputAddress, e.valuePath.String(), knownVal)
67+
return
68+
}
69+
resp.Error = fmt.Errorf("Expected unknown value at output %q path %q, but found known value", e.outputAddress, e.valuePath.String())
5870

5971
return
6072
}

plancheck/expect_unknown_output_value_at_path_test.go

Lines changed: 198 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ func Test_ExpectUnknownOutputValueAtPath_SetNestedBlock_Object(t *testing.T) {
361361
})
362362
}
363363

364-
func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue(t *testing.T) {
364+
func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue_PathNotFound(t *testing.T) {
365365
t.Parallel()
366366

367367
r.UnitTest(t, r.TestCase{
@@ -381,7 +381,201 @@ func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue(t *testing.T) {
381381
{
382382
Config: `
383383
resource "test_resource" "one" {
384-
set_attribute = ["value1"]
384+
list_nested_block {
385+
list_nested_block_attribute = "value 1"
386+
}
387+
}
388+
389+
output "resource" {
390+
value = test_resource.one
391+
}
392+
`,
393+
ConfigPlanChecks: r.ConfigPlanChecks{
394+
PreApply: []plancheck.PlanCheck{
395+
plancheck.ExpectUnknownOutputValueAtPath("resource", tfjsonpath.New("list_nested_block").AtSliceIndex(0).AtMapKey("not_correct_attr")),
396+
},
397+
},
398+
ExpectError: regexp.MustCompile(`path not found: specified key not_correct_attr not found in map at list_nested_block.0.not_correct_attr`),
399+
},
400+
},
401+
})
402+
}
403+
404+
func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue_ListAttribute(t *testing.T) {
405+
t.Parallel()
406+
407+
r.UnitTest(t, r.TestCase{
408+
ProviderFactories: map[string]func() (*schema.Provider, error){
409+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
410+
return testProvider(), nil
411+
},
412+
},
413+
// Prior to Terraform v1.3.0 a planned output is marked as fully unknown
414+
// if any attribute is unknown. The id attribute within the test provider
415+
// is unknown.
416+
// Reference: https://github.com/hashicorp/terraform/blob/v1.3/CHANGELOG.md#130-september-21-2022
417+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
418+
tfversion.SkipBelow(tfversion.Version1_3_0),
419+
},
420+
Steps: []r.TestStep{
421+
{
422+
Config: `
423+
resource "test_resource" "one" {
424+
list_attribute = ["value1"]
425+
}
426+
427+
output "resource" {
428+
value = test_resource.one
429+
}
430+
`,
431+
ConfigPlanChecks: r.ConfigPlanChecks{
432+
PreApply: []plancheck.PlanCheck{
433+
plancheck.ExpectUnknownOutputValueAtPath("resource", tfjsonpath.New("list_attribute").AtSliceIndex(0)),
434+
},
435+
},
436+
ExpectError: regexp.MustCompile(`Expected unknown value at output "resource" path "list_attribute.0", but found known value: "value1"`),
437+
},
438+
},
439+
})
440+
}
441+
442+
func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue_StringAttribute(t *testing.T) {
443+
t.Parallel()
444+
445+
r.UnitTest(t, r.TestCase{
446+
ProviderFactories: map[string]func() (*schema.Provider, error){
447+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
448+
return testProvider(), nil
449+
},
450+
},
451+
// Prior to Terraform v1.3.0 a planned output is marked as fully unknown
452+
// if any attribute is unknown. The id attribute within the test provider
453+
// is unknown.
454+
// Reference: https://github.com/hashicorp/terraform/blob/v1.3/CHANGELOG.md#130-september-21-2022
455+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
456+
tfversion.SkipBelow(tfversion.Version1_3_0),
457+
},
458+
Steps: []r.TestStep{
459+
{
460+
Config: `
461+
resource "test_resource" "one" {
462+
string_attribute = "hello world!"
463+
}
464+
465+
output "resource" {
466+
value = test_resource.one
467+
}
468+
`,
469+
ConfigPlanChecks: r.ConfigPlanChecks{
470+
PreApply: []plancheck.PlanCheck{
471+
plancheck.ExpectUnknownOutputValueAtPath("resource", tfjsonpath.New("string_attribute")),
472+
},
473+
},
474+
ExpectError: regexp.MustCompile(`Expected unknown value at output "resource" path "string_attribute", but found known value: "hello world!"`),
475+
},
476+
},
477+
})
478+
}
479+
480+
func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue_BoolAttribute(t *testing.T) {
481+
t.Parallel()
482+
483+
r.UnitTest(t, r.TestCase{
484+
ProviderFactories: map[string]func() (*schema.Provider, error){
485+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
486+
return testProvider(), nil
487+
},
488+
},
489+
// Prior to Terraform v1.3.0 a planned output is marked as fully unknown
490+
// if any attribute is unknown. The id attribute within the test provider
491+
// is unknown.
492+
// Reference: https://github.com/hashicorp/terraform/blob/v1.3/CHANGELOG.md#130-september-21-2022
493+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
494+
tfversion.SkipBelow(tfversion.Version1_3_0),
495+
},
496+
Steps: []r.TestStep{
497+
{
498+
Config: `
499+
resource "test_resource" "one" {
500+
bool_attribute = true
501+
}
502+
503+
output "resource" {
504+
value = test_resource.one
505+
}
506+
`,
507+
ConfigPlanChecks: r.ConfigPlanChecks{
508+
PreApply: []plancheck.PlanCheck{
509+
plancheck.ExpectUnknownOutputValueAtPath("resource", tfjsonpath.New("bool_attribute")),
510+
},
511+
},
512+
ExpectError: regexp.MustCompile(`Expected unknown value at output "resource" path "bool_attribute", but found known value: "true"`),
513+
},
514+
},
515+
})
516+
}
517+
518+
func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue_FloatAttribute(t *testing.T) {
519+
t.Parallel()
520+
521+
r.UnitTest(t, r.TestCase{
522+
ProviderFactories: map[string]func() (*schema.Provider, error){
523+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
524+
return testProvider(), nil
525+
},
526+
},
527+
// Prior to Terraform v1.3.0 a planned output is marked as fully unknown
528+
// if any attribute is unknown. The id attribute within the test provider
529+
// is unknown.
530+
// Reference: https://github.com/hashicorp/terraform/blob/v1.3/CHANGELOG.md#130-september-21-2022
531+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
532+
tfversion.SkipBelow(tfversion.Version1_3_0),
533+
},
534+
Steps: []r.TestStep{
535+
{
536+
Config: `
537+
resource "test_resource" "one" {
538+
float_attribute = 1.234
539+
}
540+
541+
output "resource" {
542+
value = test_resource.one
543+
}
544+
`,
545+
ConfigPlanChecks: r.ConfigPlanChecks{
546+
PreApply: []plancheck.PlanCheck{
547+
plancheck.ExpectUnknownOutputValueAtPath("resource", tfjsonpath.New("float_attribute")),
548+
},
549+
},
550+
ExpectError: regexp.MustCompile(`Expected unknown value at output "resource" path "float_attribute", but found known value: "1.234"`),
551+
},
552+
},
553+
})
554+
}
555+
556+
func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue_ListNestedBlock(t *testing.T) {
557+
t.Parallel()
558+
559+
r.UnitTest(t, r.TestCase{
560+
ProviderFactories: map[string]func() (*schema.Provider, error){
561+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
562+
return testProvider(), nil
563+
},
564+
},
565+
// Prior to Terraform v1.3.0 a planned output is marked as fully unknown
566+
// if any attribute is unknown. The id attribute within the test provider
567+
// is unknown.
568+
// Reference: https://github.com/hashicorp/terraform/blob/v1.3/CHANGELOG.md#130-september-21-2022
569+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
570+
tfversion.SkipBelow(tfversion.Version1_3_0),
571+
},
572+
Steps: []r.TestStep{
573+
{
574+
Config: `
575+
resource "test_resource" "one" {
576+
list_nested_block {
577+
list_nested_block_attribute = "value 1"
578+
}
385579
}
386580
387581
output "resource" {
@@ -390,10 +584,10 @@ func Test_ExpectUnknownOutputValueAtPath_ExpectError_KnownValue(t *testing.T) {
390584
`,
391585
ConfigPlanChecks: r.ConfigPlanChecks{
392586
PreApply: []plancheck.PlanCheck{
393-
plancheck.ExpectUnknownOutputValueAtPath("resource", tfjsonpath.New("set_attribute").AtSliceIndex(0)),
587+
plancheck.ExpectUnknownOutputValueAtPath("resource", tfjsonpath.New("list_nested_block").AtSliceIndex(0).AtMapKey("list_nested_block_attribute")),
394588
},
395589
},
396-
ExpectError: regexp.MustCompile(`attribute at path is known`),
590+
ExpectError: regexp.MustCompile(`Expected unknown value at output "resource" path "list_nested_block.0.list_nested_block_attribute", but found known value: "value 1"`),
397591
},
398592
},
399593
})

0 commit comments

Comments
 (0)