diff --git a/internal/framework5provider/dynamic_edge_resource.go b/internal/framework5provider/dynamic_edge_resource.go new file mode 100644 index 0000000..a876ecb --- /dev/null +++ b/internal/framework5provider/dynamic_edge_resource.go @@ -0,0 +1,104 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "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/types" +) + +var _ resource.Resource = DynamicEdgeResource{} + +func NewDynamicEdgeResource() resource.Resource { + return &DynamicEdgeResource{} +} + +// DynamicEdgeResource is for testing specific scenarios for dynamic schema types. +type DynamicEdgeResource struct{} + +func (r DynamicEdgeResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dynamic_edge" +} + +func (r DynamicEdgeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "required_dynamic": schema.DynamicAttribute{ + Required: true, + }, + // This computed dynamic attribute changes type during refresh + "computed_dynamic_type_changes": schema.DynamicAttribute{ + Computed: true, + }, + // id attribute is required for acceptance testing. + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r DynamicEdgeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data DynamicEdgeResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Created as a boolean type + data.ComputedDynamicTypeChanges = types.DynamicValue(types.BoolValue(true)) + + data.Id = types.StringValue("test") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r DynamicEdgeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data DynamicEdgeResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Refreshed to a string type + data.ComputedDynamicTypeChanges = types.DynamicValue(types.StringValue("it's a string!")) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r DynamicEdgeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data DynamicEdgeResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Updated to a number type + data.ComputedDynamicTypeChanges = types.DynamicValue(types.Int64Value(200)) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r DynamicEdgeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type DynamicEdgeResourceModel struct { + RequiredDynamic types.Dynamic `tfsdk:"required_dynamic"` + ComputedDynamicTypeChanges types.Dynamic `tfsdk:"computed_dynamic_type_changes"` + Id types.String `tfsdk:"id"` +} diff --git a/internal/framework5provider/dynamic_edge_resource_test.go b/internal/framework5provider/dynamic_edge_resource_test.go new file mode 100644 index 0000000..223f7a6 --- /dev/null +++ b/internal/framework5provider/dynamic_edge_resource_test.go @@ -0,0 +1,49 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// TODO: The computed dynamic value type should be allowed to change, bug will be fixed with: +// - https://github.com/hashicorp/terraform-plugin-framework/issues/969 +func TestDynamicEdge_computed_type_changes(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: `resource "framework_dynamic_edge" "test" { + required_dynamic = "value1" + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_dynamic_edge.test", tfjsonpath.New("required_dynamic"), knownvalue.StringExact("value1")), + // Created as a boolean + statecheck.ExpectKnownValue("framework_dynamic_edge.test", tfjsonpath.New("computed_dynamic_type_changes"), knownvalue.Bool(true)), + }, + }, + { + Config: `resource "framework_dynamic_edge" "test" { + required_dynamic = "new value" + }`, + // ConfigStateChecks: []statecheck.StateCheck{ + // statecheck.ExpectKnownValue("framework_dynamic_edge.test", tfjsonpath.New("required_dynamic"), knownvalue.StringExact("new value")), + // // After update, it's a number! + // statecheck.ExpectKnownValue("framework_dynamic_edge.test", tfjsonpath.New("computed_dynamic_type_changes"), knownvalue.Int64Exact(200)), + // }, + ExpectError: regexp.MustCompile(`unexpected new value: .computed_dynamic_type_changes: wrong final value type:\nstring required.`), + }, + }, + }) +} diff --git a/internal/framework5provider/provider.go b/internal/framework5provider/provider.go index d7f1e1f..89d0bbf 100644 --- a/internal/framework5provider/provider.go +++ b/internal/framework5provider/provider.go @@ -51,6 +51,7 @@ func (p *testProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ NewSchemaResource, NewDynamicSchemaResource, + NewDynamicEdgeResource, NewTimeoutsResource, NewUserResource, NewFloat64PrecisionResource, diff --git a/internal/framework6provider/dynamic_edge_resource.go b/internal/framework6provider/dynamic_edge_resource.go new file mode 100644 index 0000000..a876ecb --- /dev/null +++ b/internal/framework6provider/dynamic_edge_resource.go @@ -0,0 +1,104 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "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/types" +) + +var _ resource.Resource = DynamicEdgeResource{} + +func NewDynamicEdgeResource() resource.Resource { + return &DynamicEdgeResource{} +} + +// DynamicEdgeResource is for testing specific scenarios for dynamic schema types. +type DynamicEdgeResource struct{} + +func (r DynamicEdgeResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dynamic_edge" +} + +func (r DynamicEdgeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "required_dynamic": schema.DynamicAttribute{ + Required: true, + }, + // This computed dynamic attribute changes type during refresh + "computed_dynamic_type_changes": schema.DynamicAttribute{ + Computed: true, + }, + // id attribute is required for acceptance testing. + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r DynamicEdgeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data DynamicEdgeResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Created as a boolean type + data.ComputedDynamicTypeChanges = types.DynamicValue(types.BoolValue(true)) + + data.Id = types.StringValue("test") + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r DynamicEdgeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data DynamicEdgeResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Refreshed to a string type + data.ComputedDynamicTypeChanges = types.DynamicValue(types.StringValue("it's a string!")) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r DynamicEdgeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data DynamicEdgeResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Updated to a number type + data.ComputedDynamicTypeChanges = types.DynamicValue(types.Int64Value(200)) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r DynamicEdgeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type DynamicEdgeResourceModel struct { + RequiredDynamic types.Dynamic `tfsdk:"required_dynamic"` + ComputedDynamicTypeChanges types.Dynamic `tfsdk:"computed_dynamic_type_changes"` + Id types.String `tfsdk:"id"` +} diff --git a/internal/framework6provider/dynamic_edge_resource_test.go b/internal/framework6provider/dynamic_edge_resource_test.go new file mode 100644 index 0000000..364ecca --- /dev/null +++ b/internal/framework6provider/dynamic_edge_resource_test.go @@ -0,0 +1,49 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// TODO: The computed dynamic value type should be allowed to change, bug will be fixed with: +// - https://github.com/hashicorp/terraform-plugin-framework/issues/969 +func TestDynamicEdge_computed_type_changes(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: `resource "framework_dynamic_edge" "test" { + required_dynamic = "value1" + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("framework_dynamic_edge.test", tfjsonpath.New("required_dynamic"), knownvalue.StringExact("value1")), + // Created as a boolean + statecheck.ExpectKnownValue("framework_dynamic_edge.test", tfjsonpath.New("computed_dynamic_type_changes"), knownvalue.Bool(true)), + }, + }, + { + Config: `resource "framework_dynamic_edge" "test" { + required_dynamic = "new value" + }`, + // ConfigStateChecks: []statecheck.StateCheck{ + // statecheck.ExpectKnownValue("framework_dynamic_edge.test", tfjsonpath.New("required_dynamic"), knownvalue.StringExact("new value")), + // // After update, it's a number! + // statecheck.ExpectKnownValue("framework_dynamic_edge.test", tfjsonpath.New("computed_dynamic_type_changes"), knownvalue.Int64Exact(200)), + // }, + ExpectError: regexp.MustCompile(`unexpected new value: .computed_dynamic_type_changes: wrong final value type:\nstring required.`), + }, + }, + }) +} diff --git a/internal/framework6provider/provider.go b/internal/framework6provider/provider.go index 703c538..60e0a7e 100644 --- a/internal/framework6provider/provider.go +++ b/internal/framework6provider/provider.go @@ -50,6 +50,7 @@ func (p *testProvider) Configure(ctx context.Context, req provider.ConfigureRequ func (p *testProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ NewSchemaResource, + NewDynamicEdgeResource, NewTimeoutsResource, NewUserResource, NewFloat64PrecisionResource,