// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package backendbase import ( "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hcltest" "github.com/zclconf/go-cty-debug/ctydebug" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/tfdiags" ) func TestBase_coerceError(t *testing.T) { // This tests that we return errors if type coersion fails. // This doesn't thoroughly test all cases because we're just delegating // to the configschema package's coersion function, which is already // tested in its own package. b := Base{ Schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": { Type: cty.String, Optional: true, }, }, }, } // This is a fake body just to give us something to correlate the // diagnostic attribute paths against so we can test that the // errors are properly annotated. In the real implementation // the command package logic would evaluate the diagnostics against // the real HCL body written by the end-user. // // Because we're using MockExprLiteral for the expressions here, // the source range for each expression is just the fake filename // "MockExprLiteral". If the PrepareConfig function fails to properly // annotate its diagnostics then the source range won't be populated // at all. body := hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "foo": { Expr: hcltest.MockExprLiteral(cty.StringVal("")), }, }, }) t.Run("error", func(t *testing.T) { _, diags := b.PrepareConfig(cty.ObjectVal(map[string]cty.Value{ // This is incorrect because the schema wants a string "foo": cty.MapValEmpty(cty.String), })) // We'll use the "RPC" representation of the diagnostics just because // it's convenient for comparing without any unusual cmp options. gotDiags := diags.InConfigBody(body, "").ForRPC() var wantDiags tfdiags.Diagnostics wantDiags = wantDiags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid backend configuration", Detail: "The backend configuration is incorrect: .foo: string required.", Subject: &hcl.Range{Filename: "MockExprLiteral"}, }) wantDiags = wantDiags.ForRPC() if diff := cmp.Diff(wantDiags, gotDiags); diff != "" { t.Errorf("wrong diagnostics\n%s", diff) } }) } func TestBase_deprecatedArg(t *testing.T) { b := Base{ Schema: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "not_deprecated": { Type: cty.String, Optional: true, }, "deprecated": { Type: cty.String, Optional: true, Deprecated: true, }, }, BlockTypes: map[string]*configschema.NestedBlock{ "nested": { Nesting: configschema.NestingList, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "deprecated": { Type: cty.String, Optional: true, Deprecated: true, }, }, }, }, }, }, } // This is a fake body just to give us something to correlate the // diagnostic attribute paths against so we can test that the // warnings are properly annotated. In the real implementation // the command package logic would evaluate the diagnostics against // the real HCL body written by the end-user. // // Because we're using MockExprLiteral for the expressions here, // the source range for each expression is just the fake filename // "MockExprLiteral". If the PrepareConfig function fails to properly // annotate its diagnostics then the source range won't be populated // at all. body := hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "deprecated": { Expr: hcltest.MockExprLiteral(cty.StringVal("")), }, }, Blocks: hcl.Blocks{ { Type: "nested", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "deprecated": { Expr: hcltest.MockExprLiteral(cty.StringVal("")), }, }, }), }, { Type: "nested", Body: hcltest.MockBody(&hcl.BodyContent{ Attributes: hcl.Attributes{ "deprecated": { Expr: hcltest.MockExprLiteral(cty.StringVal("")), }, }, }), }, }, }) t.Run("nothing deprecated", func(t *testing.T) { got, diags := b.PrepareConfig(cty.ObjectVal(map[string]cty.Value{ "not_deprecated": cty.StringVal("hello"), })) if len(diags) != 0 { t.Errorf("unexpected diagnostics: %s", diags.ErrWithWarnings().Error()) } want := cty.ObjectVal(map[string]cty.Value{ "deprecated": cty.NullVal(cty.String), "not_deprecated": cty.StringVal("hello"), "nested": cty.ListValEmpty(cty.Object(map[string]cty.Type{ "deprecated": cty.String, })), }) if diff := cmp.Diff(want, got, ctydebug.CmpOptions); diff != "" { t.Errorf("wrong result\n%s", diff) } }) t.Run("toplevel deprecated", func(t *testing.T) { _, diags := b.PrepareConfig(cty.ObjectVal(map[string]cty.Value{ "deprecated": cty.StringVal("hello"), })) // We'll use the "RPC" representation of the diagnostics just because // it's convenient for comparing without any unusual cmp options. gotDiags := diags.InConfigBody(body, "").ForRPC() var wantDiags tfdiags.Diagnostics wantDiags = wantDiags.Append(&hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Deprecated provider argument", Detail: "The argument .deprecated is deprecated. Refer to the backend documentation for more information.", Subject: &hcl.Range{Filename: "MockExprLiteral"}, }) wantDiags = wantDiags.ForRPC() if diff := cmp.Diff(wantDiags, gotDiags); diff != "" { t.Errorf("wrong diagnostics\n%s", diff) } }) t.Run("nested deprecated", func(t *testing.T) { _, diags := b.PrepareConfig(cty.ObjectVal(map[string]cty.Value{ "nested": cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "deprecated": cty.StringVal("hello"), }), cty.ObjectVal(map[string]cty.Value{ "deprecated": cty.StringVal("hello"), }), }), })) // We'll use the "RPC" representation of the diagnostics just because // it's convenient for comparing without any unusual cmp options. gotDiags := diags.InConfigBody(body, "").ForRPC() var wantDiags tfdiags.Diagnostics wantDiags = wantDiags.Append(&hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Deprecated provider argument", Detail: "The argument .nested[0].deprecated is deprecated. Refer to the backend documentation for more information.", Subject: &hcl.Range{Filename: "MockExprLiteral"}, }) wantDiags = wantDiags.Append(&hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Deprecated provider argument", Detail: "The argument .nested[1].deprecated is deprecated. Refer to the backend documentation for more information.", Subject: &hcl.Range{Filename: "MockExprLiteral"}, }) wantDiags = wantDiags.ForRPC() if diff := cmp.Diff(wantDiags, gotDiags); diff != "" { t.Errorf("wrong diagnostics\n%s", diff) } }) }