Files
terraform-provider-unifi/internal/provider/validators/required_none_if_test.go
Mateusz Filipowicz 8b5ed14d8d feat: add NTP setting resource support with unifi_setting_ntp resource (#36)
* feat: add NTP setting resource support with `unifi_setting_ntp` resource

* linting

* fix missing method

* add missing validators
2025-03-02 01:10:41 +01:00

533 lines
15 KiB
Go

package validators_test
import (
"context"
"testing"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/stretchr/testify/assert"
"github.com/filipowm/terraform-provider-unifi/internal/provider/validators"
)
// Common test case structure for string conditions
type requiredNoneIfTestCase struct {
condition types.String
field1 types.String
field2 types.String
expectError bool
expectErrorText string
}
// Common test case structure for bool conditions
type requiredNoneIfBoolTestCase struct {
condition types.Bool
field1 types.String
field2 types.String
expectError bool
expectErrorText string
}
// Function to create a schema object with string condition
func createRequiredNoneIfSchema() schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"condition": schema.StringAttribute{
Optional: true,
},
"field1": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"field2": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}
// Function to create a schema object with bool condition
func createRequiredNoneIfBoolSchema() schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"condition": schema.BoolAttribute{
Optional: true,
},
"field1": schema.StringAttribute{
Optional: true,
},
"field2": schema.StringAttribute{
Optional: true,
},
},
}
}
// Function to create a config with string condition
func createRequiredNoneIfConfig(schema schema.Schema, testCase requiredNoneIfTestCase) tfsdk.Config {
return tfsdk.Config{
Schema: schema,
Raw: tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"condition": tftypes.String,
"field1": tftypes.String,
"field2": tftypes.String,
},
},
map[string]tftypes.Value{
"condition": stringToTfValue(testCase.condition),
"field1": stringToTfValue(testCase.field1),
"field2": stringToTfValue(testCase.field2),
},
),
}
}
// Function to create a config with bool condition
func createRequiredNoneIfBoolConfig(schema schema.Schema, testCase requiredNoneIfBoolTestCase) tfsdk.Config {
return tfsdk.Config{
Schema: schema,
Raw: tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"condition": tftypes.Bool,
"field1": tftypes.String,
"field2": tftypes.String,
},
},
map[string]tftypes.Value{
"condition": boolToTfValue(testCase.condition),
"field1": stringToTfValue(testCase.field1),
"field2": stringToTfValue(testCase.field2),
},
),
}
}
// Test RequiredNoneIf with string condition
func TestRequiredNoneIf(t *testing.T) {
testCases := map[string]requiredNoneIfTestCase{
"matching_condition_all_configured": {
condition: types.StringValue("test"),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: true,
expectErrorText: "If \"condition\" equals \"test\", any of those attributes must not be configured: [field1,field2]",
},
"matching_condition_one_configured": {
condition: types.StringValue("test"),
field1: types.StringValue("value1"),
field2: types.StringNull(),
expectError: true,
expectErrorText: "If \"condition\" equals \"test\", any of those attributes must not be configured: [field1,field2]",
},
"matching_condition_none_configured": {
condition: types.StringValue("test"),
field1: types.StringNull(),
field2: types.StringNull(),
expectError: false,
},
"non_matching_condition_all_configured": {
condition: types.StringValue("non-test"),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: false,
},
"matching_condition_unknown_values": {
condition: types.StringValue("test"),
field1: types.StringUnknown(),
field2: types.StringValue("value2"),
expectError: false, // Unknown values should skip validation
},
"null_condition_all_configured": {
condition: types.StringNull(),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: false,
},
"unknown_condition_all_configured": {
condition: types.StringUnknown(),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: false,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
schema := createRequiredNoneIfSchema()
config := createRequiredNoneIfConfig(schema, testCase)
validator := validators.RequiredNoneIf(
path.MatchRoot("condition"),
types.StringValue("test"),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
diagnostics := validator.Validate(ctx, config)
if testCase.expectError {
assert.True(t, diagnostics.HasError())
if testCase.expectErrorText != "" {
found := false
for _, diag := range diagnostics {
if diag.Detail() != "" && diag.Detail() == testCase.expectErrorText {
found = true
break
}
}
assert.True(t, found, "Expected error text not found")
}
} else {
assert.False(t, diagnostics.HasError())
}
})
}
}
// Test RequiredNoneIfSet with string condition
func TestRequiredNoneIfSet(t *testing.T) {
testCases := map[string]requiredNoneIfTestCase{
"condition_set_all_configured": {
condition: types.StringValue("any-value"),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: true,
expectErrorText: "If \"condition\" is set, any of those attributes must not be configured: [field1,field2]",
},
"condition_set_one_configured": {
condition: types.StringValue("any-value"),
field1: types.StringValue("value1"),
field2: types.StringNull(),
expectError: true,
expectErrorText: "If \"condition\" is set, any of those attributes must not be configured: [field1,field2]",
},
"condition_set_none_configured": {
condition: types.StringValue("any-value"),
field1: types.StringNull(),
field2: types.StringNull(),
expectError: false,
},
"condition_null_all_configured": {
condition: types.StringNull(),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: false,
},
"condition_unknown_all_configured": {
condition: types.StringUnknown(),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: false,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
schema := createRequiredNoneIfSchema()
config := createRequiredNoneIfConfig(schema, testCase)
validator := validators.RequiredNoneIfSet(
path.MatchRoot("condition"),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
diagnostics := validator.Validate(ctx, config)
if testCase.expectError {
assert.True(t, diagnostics.HasError())
if testCase.expectErrorText != "" {
found := false
for _, diag := range diagnostics {
if diag.Detail() != "" && diag.Detail() == testCase.expectErrorText {
found = true
break
}
}
assert.True(t, found, "Expected error text not found")
}
} else {
assert.False(t, diagnostics.HasError())
}
})
}
}
// Test RequiredNoneIf with boolean condition
func TestRequiredNoneIfWithBoolCondition(t *testing.T) {
testCases := map[string]requiredNoneIfBoolTestCase{
"matching_true_condition_all_configured": {
condition: types.BoolValue(true),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: true,
expectErrorText: "If \"condition\" equals true, any of those attributes must not be configured: [field1,field2]",
},
"matching_true_condition_none_configured": {
condition: types.BoolValue(true),
field1: types.StringNull(),
field2: types.StringNull(),
expectError: false,
},
"non_matching_false_condition_all_configured": {
condition: types.BoolValue(false),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: false,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
schema := createRequiredNoneIfBoolSchema()
config := createRequiredNoneIfBoolConfig(schema, testCase)
validator := validators.RequiredNoneIf(
path.MatchRoot("condition"),
types.BoolValue(true),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
diagnostics := validator.Validate(ctx, config)
if testCase.expectError {
assert.True(t, diagnostics.HasError())
if testCase.expectErrorText != "" {
found := false
for _, diag := range diagnostics {
if diag.Detail() != "" && diag.Detail() == testCase.expectErrorText {
found = true
break
}
}
assert.True(t, found, "Expected error text not found")
}
} else {
assert.False(t, diagnostics.HasError())
}
})
}
}
// Test ValidateDataSource method
func TestRequiredNoneIfValidateDataSource(t *testing.T) {
testCases := map[string]requiredNoneIfTestCase{
"matching_condition_all_configured": {
condition: types.StringValue("test"),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: true,
},
"matching_condition_none_configured": {
condition: types.StringValue("test"),
field1: types.StringNull(),
field2: types.StringNull(),
expectError: false,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
schema := createRequiredNoneIfSchema()
config := createRequiredNoneIfConfig(schema, testCase)
validator := validators.RequiredNoneIf(
path.MatchRoot("condition"),
types.StringValue("test"),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
request := datasource.ValidateConfigRequest{
Config: config,
}
response := &datasource.ValidateConfigResponse{}
validator.ValidateDataSource(ctx, request, response)
if testCase.expectError {
assert.True(t, response.Diagnostics.HasError())
} else {
assert.False(t, response.Diagnostics.HasError())
}
})
}
}
// Test ValidateProvider method
func TestRequiredNoneIfValidateProvider(t *testing.T) {
testCases := map[string]requiredNoneIfTestCase{
"matching_condition_all_configured": {
condition: types.StringValue("test"),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: true,
},
"matching_condition_none_configured": {
condition: types.StringValue("test"),
field1: types.StringNull(),
field2: types.StringNull(),
expectError: false,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
schema := createRequiredNoneIfSchema()
config := createRequiredNoneIfConfig(schema, testCase)
validator := validators.RequiredNoneIf(
path.MatchRoot("condition"),
types.StringValue("test"),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
request := provider.ValidateConfigRequest{
Config: config,
}
response := &provider.ValidateConfigResponse{}
validator.ValidateProvider(ctx, request, response)
if testCase.expectError {
assert.True(t, response.Diagnostics.HasError())
} else {
assert.False(t, response.Diagnostics.HasError())
}
})
}
}
// Test ValidateResource method
func TestRequiredNoneIfValidateResource(t *testing.T) {
testCases := map[string]requiredNoneIfTestCase{
"matching_condition_all_configured": {
condition: types.StringValue("test"),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
expectError: true,
},
"matching_condition_none_configured": {
condition: types.StringValue("test"),
field1: types.StringNull(),
field2: types.StringNull(),
expectError: false,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
schema := createRequiredNoneIfSchema()
config := createRequiredNoneIfConfig(schema, testCase)
validator := validators.RequiredNoneIf(
path.MatchRoot("condition"),
types.StringValue("test"),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
request := resource.ValidateConfigRequest{
Config: config,
}
response := &resource.ValidateConfigResponse{}
validator.ValidateResource(ctx, request, response)
if testCase.expectError {
assert.True(t, response.Diagnostics.HasError())
} else {
assert.False(t, response.Diagnostics.HasError())
}
})
}
}
// Test the Description and MarkdownDescription methods for both variants
func TestRequiredNoneIfDescription(t *testing.T) {
t.Run("RequiredNoneIf description", func(t *testing.T) {
validator := validators.RequiredNoneIf(
path.MatchRoot("condition"),
types.StringValue("test"),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
ctx := context.Background()
desc := validator.Description(ctx)
mdDesc := validator.MarkdownDescription(ctx)
expectedDesc := `If "condition" equals "test", any of those attributes must not be configured: [field1,field2]`
assert.Equal(t, expectedDesc, desc)
assert.Equal(t, expectedDesc, mdDesc)
})
t.Run("RequiredNoneIfSet description", func(t *testing.T) {
validator := validators.RequiredNoneIfSet(
path.MatchRoot("condition"),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
ctx := context.Background()
desc := validator.Description(ctx)
mdDesc := validator.MarkdownDescription(ctx)
expectedDesc := `If "condition" is set, any of those attributes must not be configured: [field1,field2]`
assert.Equal(t, expectedDesc, desc)
assert.Equal(t, expectedDesc, mdDesc)
})
}
// Test with missing path
func TestRequiredNoneIfWithMissingPath(t *testing.T) {
ctx := context.Background()
schema := createRequiredNoneIfSchema()
testCase := requiredNoneIfTestCase{
condition: types.StringValue("test"),
field1: types.StringValue("value1"),
field2: types.StringValue("value2"),
}
config := createRequiredNoneIfConfig(schema, testCase)
validator := validators.RequiredNoneIf(
path.MatchRoot("non_existent"),
types.StringValue("test"),
path.MatchRoot("field1"),
path.MatchRoot("field2"),
)
diagnostics := validator.Validate(ctx, config)
// Should not get an error because the condition path doesn't match
assert.False(t, diagnostics.HasError())
}