mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-22 19:24:37 -05:00
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org> Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
300 lines
8.8 KiB
Go
300 lines
8.8 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package marks
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty-debug/ctydebug"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// Integration test for the marks to see if consolidate warnings is
|
|
// checking by the key of the mark together with the address
|
|
func TestMarkConsolidateWarnings(t *testing.T) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
for i := 0; i < 2; i++ {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
// Summary is the same for both diagnostics, but key is different
|
|
Summary: "Output deprecated",
|
|
Detail: fmt.Sprintf("This one has an output %d", i),
|
|
Subject: &hcl.Range{
|
|
Filename: "foo.tf",
|
|
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
|
End: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
|
},
|
|
Extra: DeprecationCause{
|
|
By: addrs.OutputValue{
|
|
Name: "output",
|
|
},
|
|
Key: fmt.Sprintf("output%d", i),
|
|
Message: "output deprecate",
|
|
},
|
|
})
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: "Variable deprecated",
|
|
Detail: fmt.Sprintf("This one has a var %d", i),
|
|
Subject: &hcl.Range{
|
|
Filename: "foo.tf",
|
|
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
|
End: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
|
},
|
|
Extra: DeprecationCause{
|
|
By: addrs.InputVariable{
|
|
Name: "variable",
|
|
},
|
|
Key: fmt.Sprintf("variable%d", i),
|
|
Message: "variable deprecate",
|
|
},
|
|
})
|
|
}
|
|
|
|
// Adding an extra diagnostic with the same key to be consolidated
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: "Variable deprecated",
|
|
Detail: "This one has a var 1",
|
|
Subject: &hcl.Range{
|
|
Filename: "foo.tf",
|
|
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
|
End: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
|
},
|
|
Extra: DeprecationCause{
|
|
By: addrs.InputVariable{
|
|
Name: "variable",
|
|
},
|
|
Key: "variable1",
|
|
Message: "variable deprecate",
|
|
},
|
|
})
|
|
|
|
// Adding an extra diagnostic with a variable in a module
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: "Variable deprecated",
|
|
Detail: "This one has a var 1 in a module",
|
|
Subject: &hcl.Range{
|
|
Filename: "foo.tf",
|
|
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
|
End: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
|
},
|
|
Extra: DeprecationCause{
|
|
By: addrs.InputVariable{
|
|
Name: "mod1.variable",
|
|
},
|
|
Key: "mod1.variable1",
|
|
Message: "variable deprecate",
|
|
},
|
|
})
|
|
|
|
consolidatedDiags := diags.Consolidate(1, tfdiags.Warning)
|
|
expectedDescriptions := [][2]string{
|
|
{"Output deprecated", "This one has an output 0"},
|
|
{"Variable deprecated", "This one has a var 0"},
|
|
{"Output deprecated", "This one has an output 1"},
|
|
{"Variable deprecated", "This one has a var 1\n\n(and one more similar warning elsewhere)"},
|
|
{"Variable deprecated", "This one has a var 1 in a module"},
|
|
}
|
|
|
|
// We created 5 diagnostics, but the last one is consolidated
|
|
expectedLen := len(expectedDescriptions)
|
|
if len(consolidatedDiags) != expectedLen {
|
|
t.Errorf("len %d is expected, got %d", expectedLen, len(consolidatedDiags))
|
|
}
|
|
|
|
for i, vals := range expectedDescriptions {
|
|
if diff := cmp.Diff(vals[0], consolidatedDiags[i].Description().Summary); diff != "" {
|
|
t.Errorf("%d: wrong summary: %s", i, diff)
|
|
}
|
|
if diff := cmp.Diff(vals[1], consolidatedDiags[i].Description().Detail); diff != "" {
|
|
t.Errorf("%d: wrong detail msg: %s", i, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHasDeprecated(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input cty.Value
|
|
want bool
|
|
}{
|
|
{
|
|
name: "no marks",
|
|
input: cty.StringVal("test"),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "only sensitive mark",
|
|
input: cty.StringVal("test").Mark(Sensitive),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "has deprecation mark",
|
|
input: Deprecated(cty.StringVal("test"), DeprecationCause{
|
|
By: addrs.InputVariable{Name: "var1"},
|
|
Key: "var1",
|
|
Message: "deprecated",
|
|
}),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "mixed marks with deprecation",
|
|
input: Deprecated(cty.StringVal("test").Mark(Sensitive), DeprecationCause{
|
|
By: addrs.InputVariable{Name: "var1"},
|
|
Key: "var1",
|
|
Message: "deprecated",
|
|
}),
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := HasDeprecated(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("HasDeprecated() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractDeprecatedDiagnosticsWithExpr(t *testing.T) {
|
|
// This is a small unit test focused just on how we detect a deprecation
|
|
// mark and translate it into a diagnostic message. The main tests for
|
|
// the overall dynamic deprecation handling are in
|
|
// [tofu.TestContext2Apply_deprecationWarnings].
|
|
|
|
input := cty.ObjectVal(map[string]cty.Value{
|
|
"okay": cty.StringVal("not deprecated").Mark(Sensitive),
|
|
"warn": DeprecatedOutput(
|
|
cty.StringVal("deprecated"),
|
|
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance.Child("child", addrs.StringKey("beep"))),
|
|
"Blah blah blah don't use this!",
|
|
false,
|
|
),
|
|
})
|
|
got, gotDiags := ExtractDeprecatedDiagnosticsWithExpr(
|
|
input,
|
|
// This expression is used just for its source location information.
|
|
hcl.StaticExpr(cty.DynamicVal, hcl.Range{Filename: "test.tofu"}),
|
|
)
|
|
want := cty.ObjectVal(map[string]cty.Value{
|
|
"okay": cty.StringVal("not deprecated").Mark(Sensitive), // non-deprecation marks should be preserved
|
|
"warn": cty.StringVal("deprecated"), // deprecation marks are removed
|
|
})
|
|
if diff := cmp.Diff(want, got, ctydebug.CmpOptions); diff != "" {
|
|
t.Error("wrong result value\n" + diff)
|
|
}
|
|
|
|
// We'll use the "ForRPC" representation of diagnostics just because it
|
|
// compares well with cmp. We don't actually care what type of diagnostic
|
|
// is returned here, only that it has the expected content.
|
|
gotDiags = gotDiags.ForRPC()
|
|
var wantDiags tfdiags.Diagnostics
|
|
wantDiags = wantDiags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: "Value derived from a deprecated source",
|
|
Detail: "This value's attribute warn is derived from module.child[\"beep\"].foo, which is deprecated with the following message:\n\nBlah blah blah don't use this!",
|
|
Subject: &hcl.Range{Filename: "test.tofu"}, // source location should come from the given expression
|
|
})
|
|
wantDiags = wantDiags.ForRPC()
|
|
if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
|
|
t.Error("wrong diagnostics\n" + diff)
|
|
}
|
|
|
|
}
|
|
|
|
func TestContainsMarks(t *testing.T) {
|
|
cases := []struct {
|
|
v cty.Value
|
|
check map[valueMark]bool
|
|
|
|
wantOnContainsMarks bool
|
|
}{
|
|
{
|
|
cty.StringVal("test").Mark(Ephemeral).Mark(Sensitive),
|
|
map[valueMark]bool{Ephemeral: true, Sensitive: true},
|
|
true,
|
|
},
|
|
{
|
|
cty.StringVal("test").Mark(Ephemeral),
|
|
map[valueMark]bool{Ephemeral: true, Sensitive: false},
|
|
true,
|
|
},
|
|
{
|
|
cty.StringVal("test").Mark(Sensitive),
|
|
map[valueMark]bool{Ephemeral: false, Sensitive: true},
|
|
true,
|
|
},
|
|
{
|
|
cty.StringVal("test").Mark(valueMark("non-existing-mark")),
|
|
map[valueMark]bool{Ephemeral: false, Sensitive: false},
|
|
false,
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"nested": cty.ObjectVal(map[string]cty.Value{
|
|
"set": cty.SetVal([]cty.Value{cty.NumberIntVal(42).Mark(Ephemeral).Mark(Sensitive)}),
|
|
}),
|
|
}),
|
|
map[valueMark]bool{Ephemeral: true, Sensitive: true},
|
|
true,
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"nested": cty.ObjectVal(map[string]cty.Value{
|
|
"set": cty.SetVal([]cty.Value{cty.NumberIntVal(42).Mark(Ephemeral)}),
|
|
}),
|
|
}),
|
|
map[valueMark]bool{Ephemeral: true, Sensitive: false},
|
|
true,
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"nested": cty.ObjectVal(map[string]cty.Value{
|
|
"set": cty.SetVal([]cty.Value{cty.NumberIntVal(42).Mark(Sensitive)}),
|
|
}),
|
|
}),
|
|
map[valueMark]bool{Ephemeral: false, Sensitive: true},
|
|
true,
|
|
},
|
|
{
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
"nested": cty.ObjectVal(map[string]cty.Value{
|
|
"set": cty.SetVal([]cty.Value{cty.NumberIntVal(42).Mark(valueMark("non-existing-mark"))}),
|
|
}),
|
|
}),
|
|
map[valueMark]bool{Ephemeral: false, Sensitive: false},
|
|
false,
|
|
},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.v.GoString(), func(t *testing.T) {
|
|
var allMarks []valueMark
|
|
for mark, want := range tt.check {
|
|
allMarks = append(allMarks, mark)
|
|
if got := Contains(tt.v, mark); want != got {
|
|
t.Errorf("Contains - expected mark %s to return %t but got %t", mark, want, got)
|
|
}
|
|
}
|
|
|
|
if got := ContainsAnyMark(tt.v, allMarks...); tt.wantOnContainsMarks != got {
|
|
t.Errorf("ContainsAnyMark - expected checking marks %#v to return %t but got %t", allMarks, tt.wantOnContainsMarks, got)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|