From d4e66cb9b45a2c48ccbfa408020e64dec0147df5 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Thu, 2 Jun 2022 17:40:38 -0400 Subject: [PATCH] internal: Implement fwserver ApplyResourceChange testing and update proto6server testing Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 --- .../server_applyresourcechange_test.go | 855 +++++- .../fwserver/server_createresource_test.go | 251 +- .../fwserver/server_deleteresource_test.go | 222 +- .../fwserver/server_updateresource_test.go | 410 ++- .../server_applyresourcechange_test.go | 2317 ++++++----------- internal/testing/testprovider/resource.go | 8 +- 6 files changed, 2494 insertions(+), 1569 deletions(-) diff --git a/internal/fwserver/server_applyresourcechange_test.go b/internal/fwserver/server_applyresourcechange_test.go index 76ab5a904..9cd037f4a 100644 --- a/internal/fwserver/server_applyresourcechange_test.go +++ b/internal/fwserver/server_applyresourcechange_test.go @@ -5,31 +5,862 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" - "github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) -// TODO: Migrate tfsdk.Provider bits of proto6server.testProviderServer to -// new internal/testing/provider.Provider that allows customization of all -// method implementations via struct fields. Then, create additional test -// cases in this unit test. -// -// For now this testing is covered by proto6server.ApplyResourceChange. -// -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 func TestServerApplyResourceChange(t *testing.T) { t.Parallel() + testSchemaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_computed": tftypes.String, + "test_required": tftypes.String, + }, + } + + testSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_computed": { + Computed: true, + Type: types.StringType, + }, + "test_required": { + Required: true, + Type: types.StringType, + }, + }, + } + + testEmptyPlan := &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, nil), + Schema: testSchema, + } + + testEmptyState := &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, nil), + Schema: testSchema, + } + + type testSchemaData struct { + TestComputed types.String `tfsdk:"test_computed"` + TestRequired types.String `tfsdk:"test_required"` + } + + testProviderMetaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_provider_meta_attribute": tftypes.String, + }, + } + + testProviderMetaValue := tftypes.NewValue(testProviderMetaType, map[string]tftypes.Value{ + "test_provider_meta_attribute": tftypes.NewValue(tftypes.String, "test-provider-meta-value"), + }) + + testProviderMetaSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_provider_meta_attribute": { + Optional: true, + Type: types.StringType, + }, + }, + } + + testProviderMetaConfig := &tfsdk.Config{ + Raw: testProviderMetaValue, + Schema: testProviderMetaSchema, + } + + type testProviderMetaData struct { + TestProviderMetaAttribute types.String `tfsdk:"test_provider_meta_attribute"` + } + testCases := map[string]struct { server *fwserver.Server request *fwserver.ApplyResourceChangeRequest expectedResponse *fwserver.ApplyResourceChangeResponse }{ - "empty-provider": { + "create-request-config": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + Schema: testSchema, + }, + PriorState: testEmptyState, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-config-value" { + resp.Diagnostics.AddError("unexpected req.Config value: %s", data.TestRequired.Value) + } + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: testEmptyState, + }, + }, + "create-request-plannedstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + }), + Schema: testSchema, + }, + PriorState: testEmptyState, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-plannedstate-value" { + resp.Diagnostics.AddError("unexpected req.Plan value: %s", data.TestRequired.Value) + } + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: testEmptyState, + }, + }, + "create-request-providermeta": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + PriorState: testEmptyState, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("unexpected req.ProviderMeta value: %s", data.TestProviderMetaAttribute.Value) + } + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + ProviderMeta: testProviderMetaConfig, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: testEmptyState, + }, + }, + "create-response-diagnostics": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + PriorState: testEmptyState, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic( + "warning summary", + "warning detail", + ), + diag.NewErrorDiagnostic( + "error summary", + "error detail", + ), + }, + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: testEmptyState, + }, + }, + "create-response-newstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + Schema: testSchema, + }, + PriorState: testEmptyState, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + Schema: testSchema, + }, + }, + }, + "delete-request-priorstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + PlannedState: testEmptyPlan, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-priorstate-value" { + resp.Diagnostics.AddError("unexpected req.State value: %s", data.TestRequired.Value) + } + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + NewState: testEmptyState, + }, + }, + "delete-request-providermeta": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + PlannedState: testEmptyPlan, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("unexpected req.ProviderMeta value: %s", data.TestProviderMetaAttribute.Value) + } + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, nil + }, + }, + ProviderMeta: testProviderMetaConfig, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + NewState: testEmptyState, + }, + }, + "delete-response-diagnostics": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + PlannedState: testEmptyPlan, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic( + "warning summary", + "warning detail", + ), + diag.NewErrorDiagnostic( + "error summary", + "error detail", + ), + }, + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + }, + }, + "delete-response-newstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + PlannedState: testEmptyPlan, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + // Intentionally empty, should call resp.State.RemoveResource() automatically. + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + NewState: testEmptyState, + }, + }, + "update-request-config": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-new-value" { + resp.Diagnostics.AddError("Unexpected req.Config Value", "Got: "+data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "update-request-plannedstate": { server: &fwserver.Server{ - Provider: &emptyprovider.Provider{}, + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if data.TestComputed.Value != "test-plannedstate-value" { + resp.Diagnostics.AddError("Unexpected req.Plan Value", "Got: "+data.TestComputed.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "update-request-priorstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-old-value" { + resp.Diagnostics.AddError("Unexpected req.State Value", "Got: "+data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "update-request-providermeta": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ProviderMeta: testProviderMetaConfig, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("Unexpected req.ProviderMeta Value", "Got: "+data.TestProviderMetaAttribute.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "update-response-diagnostics": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic( + "warning summary", + "warning detail", + ), + diag.NewErrorDiagnostic( + "error summary", + "error detail", + ), + }, + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "update-response-newstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, }, - expectedResponse: &fwserver.ApplyResourceChangeResponse{}, }, } diff --git a/internal/fwserver/server_createresource_test.go b/internal/fwserver/server_createresource_test.go index df185338a..97bdbdaf2 100644 --- a/internal/fwserver/server_createresource_test.go +++ b/internal/fwserver/server_createresource_test.go @@ -5,31 +5,258 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" - "github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) -// TODO: Migrate tfsdk.Provider bits of proto6server.testProviderServer to -// new internal/testing/provider.Provider that allows customization of all -// method implementations via struct fields. Then, create additional test -// cases in this unit test. -// -// For now this testing is covered by proto6server.CreateResource. -// -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 func TestServerCreateResource(t *testing.T) { t.Parallel() + testSchemaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_computed": tftypes.String, + "test_required": tftypes.String, + }, + } + + testSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_computed": { + Computed: true, + Type: types.StringType, + }, + "test_required": { + Required: true, + Type: types.StringType, + }, + }, + } + + testEmptyState := &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, nil), + Schema: testSchema, + } + + type testSchemaData struct { + TestComputed types.String `tfsdk:"test_computed"` + TestRequired types.String `tfsdk:"test_required"` + } + + testProviderMetaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_provider_meta_attribute": tftypes.String, + }, + } + + testProviderMetaValue := tftypes.NewValue(testProviderMetaType, map[string]tftypes.Value{ + "test_provider_meta_attribute": tftypes.NewValue(tftypes.String, "test-provider-meta-value"), + }) + + testProviderMetaSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_provider_meta_attribute": { + Optional: true, + Type: types.StringType, + }, + }, + } + + testProviderMetaConfig := &tfsdk.Config{ + Raw: testProviderMetaValue, + Schema: testProviderMetaSchema, + } + + type testProviderMetaData struct { + TestProviderMetaAttribute types.String `tfsdk:"test_provider_meta_attribute"` + } + testCases := map[string]struct { server *fwserver.Server request *fwserver.CreateResourceRequest expectedResponse *fwserver.CreateResourceResponse }{ - "empty-provider": { + "request-config": { server: &fwserver.Server{ - Provider: &emptyprovider.Provider{}, + Provider: &testprovider.Provider{}, + }, + request: &fwserver.CreateResourceRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-config-value" { + resp.Diagnostics.AddError("Unexpected req.Config Value", "Got: "+data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.CreateResourceResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: testEmptyState, + }, + }, + "request-plannedstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.CreateResourceRequest{ + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-plannedstate-value" { + resp.Diagnostics.AddError("Unexpected req.Plan Value", "Got: "+data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.CreateResourceResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: testEmptyState, + }, + }, + "request-providermeta": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.CreateResourceRequest{ + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("Unexpected req.ProviderMeta Value", "Got: "+data.TestProviderMetaAttribute.Value) + } + }, + }, nil + }, + }, + ProviderMeta: testProviderMetaConfig, + }, + expectedResponse: &fwserver.CreateResourceResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: testEmptyState, + }, + }, + "response-diagnostics": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.CreateResourceRequest{ + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.CreateResourceResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic( + "warning summary", + "warning detail", + ), + diag.NewErrorDiagnostic( + "error summary", + "error detail", + ), + }, + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: testEmptyState, + }, + }, + "response-newstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.CreateResourceRequest{ + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.CreateResourceResponse{ + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + }), + Schema: testSchema, + }, }, - expectedResponse: &fwserver.CreateResourceResponse{}, }, } diff --git a/internal/fwserver/server_deleteresource_test.go b/internal/fwserver/server_deleteresource_test.go index c4b9dd525..208243550 100644 --- a/internal/fwserver/server_deleteresource_test.go +++ b/internal/fwserver/server_deleteresource_test.go @@ -5,31 +5,229 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" - "github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) -// TODO: Migrate tfsdk.Provider bits of proto6server.testProviderServer to -// new internal/testing/provider.Provider that allows customization of all -// method implementations via struct fields. Then, create additional test -// cases in this unit test. -// -// For now this testing is covered by proto6server.DeleteResource. -// -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 func TestServerDeleteResource(t *testing.T) { t.Parallel() + testSchemaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_computed": tftypes.String, + "test_required": tftypes.String, + }, + } + + testSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_computed": { + Computed: true, + Type: types.StringType, + }, + "test_required": { + Required: true, + Type: types.StringType, + }, + }, + } + + testEmptyState := &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, nil), + Schema: testSchema, + } + + type testSchemaData struct { + TestComputed types.String `tfsdk:"test_computed"` + TestRequired types.String `tfsdk:"test_required"` + } + + testProviderMetaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_provider_meta_attribute": tftypes.String, + }, + } + + testProviderMetaValue := tftypes.NewValue(testProviderMetaType, map[string]tftypes.Value{ + "test_provider_meta_attribute": tftypes.NewValue(tftypes.String, "test-provider-meta-value"), + }) + + testProviderMetaSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_provider_meta_attribute": { + Optional: true, + Type: types.StringType, + }, + }, + } + + testProviderMetaConfig := &tfsdk.Config{ + Raw: testProviderMetaValue, + Schema: testProviderMetaSchema, + } + + type testProviderMetaData struct { + TestProviderMetaAttribute types.String `tfsdk:"test_provider_meta_attribute"` + } + testCases := map[string]struct { server *fwserver.Server request *fwserver.DeleteResourceRequest expectedResponse *fwserver.DeleteResourceResponse }{ - "empty-provider": { + "request-priorstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.DeleteResourceRequest{ + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-priorstate-value" { + resp.Diagnostics.AddError("unexpected req.State value: %s", data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.DeleteResourceResponse{ + NewState: testEmptyState, + }, + }, + "request-providermeta": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.DeleteResourceRequest{ + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("unexpected req.ProviderMeta value: %s", data.TestProviderMetaAttribute.Value) + } + }, + }, nil + }, + }, + ProviderMeta: testProviderMetaConfig, + }, + expectedResponse: &fwserver.DeleteResourceResponse{ + NewState: testEmptyState, + }, + }, + "response-diagnostics": { server: &fwserver.Server{ - Provider: &emptyprovider.Provider{}, + Provider: &testprovider.Provider{}, + }, + request: &fwserver.DeleteResourceRequest{ + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.DeleteResourceResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic( + "warning summary", + "warning detail", + ), + diag.NewErrorDiagnostic( + "error summary", + "error detail", + ), + }, + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + }, + }, + "response-newstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.DeleteResourceRequest{ + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + // Intentionally empty, should call resp.State.RemoveResource() automatically. + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.DeleteResourceResponse{ + NewState: testEmptyState, }, - expectedResponse: &fwserver.DeleteResourceResponse{}, }, } diff --git a/internal/fwserver/server_updateresource_test.go b/internal/fwserver/server_updateresource_test.go index eef3da898..9f572fd65 100644 --- a/internal/fwserver/server_updateresource_test.go +++ b/internal/fwserver/server_updateresource_test.go @@ -5,31 +5,417 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" - "github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) -// TODO: Migrate tfsdk.Provider bits of proto6server.testProviderServer to -// new internal/testing/provider.Provider that allows customization of all -// method implementations via struct fields. Then, create additional test -// cases in this unit test. -// -// For now this testing is covered by proto6server.UpdateResource. -// -// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 func TestServerUpdateResource(t *testing.T) { t.Parallel() + testSchemaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_computed": tftypes.String, + "test_required": tftypes.String, + }, + } + + testSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_computed": { + Computed: true, + Type: types.StringType, + }, + "test_required": { + Required: true, + Type: types.StringType, + }, + }, + } + + type testSchemaData struct { + TestComputed types.String `tfsdk:"test_computed"` + TestRequired types.String `tfsdk:"test_required"` + } + + testProviderMetaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_provider_meta_attribute": tftypes.String, + }, + } + + testProviderMetaValue := tftypes.NewValue(testProviderMetaType, map[string]tftypes.Value{ + "test_provider_meta_attribute": tftypes.NewValue(tftypes.String, "test-provider-meta-value"), + }) + + testProviderMetaSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_provider_meta_attribute": { + Optional: true, + Type: types.StringType, + }, + }, + } + + testProviderMetaConfig := &tfsdk.Config{ + Raw: testProviderMetaValue, + Schema: testProviderMetaSchema, + } + + type testProviderMetaData struct { + TestProviderMetaAttribute types.String `tfsdk:"test_provider_meta_attribute"` + } + testCases := map[string]struct { server *fwserver.Server request *fwserver.UpdateResourceRequest expectedResponse *fwserver.UpdateResourceResponse }{ - "empty-provider": { + "request-config": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.UpdateResourceRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-new-value" { + resp.Diagnostics.AddError("Unexpected req.Config Value", "Got: "+data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.UpdateResourceResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "request-plannedstate": { server: &fwserver.Server{ - Provider: &emptyprovider.Provider{}, + Provider: &testprovider.Provider{}, + }, + request: &fwserver.UpdateResourceRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if data.TestComputed.Value != "test-plannedstate-value" { + resp.Diagnostics.AddError("Unexpected req.Plan Value", "Got: "+data.TestComputed.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.UpdateResourceResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "request-priorstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.UpdateResourceRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-old-value" { + resp.Diagnostics.AddError("Unexpected req.State Value", "Got: "+data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.UpdateResourceResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "request-providermeta": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.UpdateResourceRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ProviderMeta: testProviderMetaConfig, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("Unexpected req.ProviderMeta Value", "Got: "+data.TestProviderMetaAttribute.Value) + } + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.UpdateResourceResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "response-diagnostics": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.UpdateResourceRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.UpdateResourceResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic( + "warning summary", + "warning detail", + ), + diag.NewErrorDiagnostic( + "error summary", + "error detail", + ), + }, + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + }, + }, + "response-newstate": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.UpdateResourceRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PlannedState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + Schema: testSchema, + }, + ResourceSchema: testSchema, + ResourceType: &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + }, + }, nil + }, + }, + }, + expectedResponse: &fwserver.UpdateResourceResponse{ + NewState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + Schema: testSchema, + }, }, - expectedResponse: &fwserver.UpdateResourceResponse{}, }, } diff --git a/internal/proto6server/server_applyresourcechange_test.go b/internal/proto6server/server_applyresourcechange_test.go index 7a09df654..fa2044349 100644 --- a/internal/proto6server/server_applyresourcechange_test.go +++ b/internal/proto6server/server_applyresourcechange_test.go @@ -5,8 +5,11 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider" "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -14,1625 +17,905 @@ import ( func TestServerApplyResourceChange(t *testing.T) { t.Parallel() - type testCase struct { - // request input - priorState tftypes.Value - plannedState tftypes.Value - config tftypes.Value - plannedPrivate []byte - providerMeta tftypes.Value - resource string - action string - resourceType tftypes.Type + testSchemaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_computed": tftypes.String, + "test_required": tftypes.String, + }, + } - create func(context.Context, tfsdk.CreateResourceRequest, *tfsdk.CreateResourceResponse) - update func(context.Context, tfsdk.UpdateResourceRequest, *tfsdk.UpdateResourceResponse) - destroy func(context.Context, tfsdk.DeleteResourceRequest, *tfsdk.DeleteResourceResponse) + testEmptyDynamicValue, _ := tfprotov6.NewDynamicValue(testSchemaType, tftypes.NewValue(testSchemaType, nil)) - // response expectations - expectedNewState tftypes.Value - expectedDiags []*tfprotov6.Diagnostic - expectedPrivate []byte + testSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_computed": { + Computed: true, + Type: types.StringType, + }, + "test_required": { + Required: true, + Type: types.StringType, + }, + }, } - tests := map[string]testCase{ - "one_create": { - plannedState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, nil), - }), - resource: "test_one", - action: "create", - resourceType: testServeResourceTypeOneType, - create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }) - }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), + type testSchemaData struct { + TestComputed types.String `tfsdk:"test_computed"` + TestRequired types.String `tfsdk:"test_required"` + } + + testProviderMetaType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_provider_meta_attribute": tftypes.String, }, - "one_create_diags": { - plannedState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, nil), - }), - resource: "test_one", - action: "create", - resourceType: testServeResourceTypeOneType, - create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }) - resp.Diagnostics.AddAttributeWarning( - tftypes.NewAttributePath().WithAttributeName("favorite_colors").WithElementKeyInt(0), - "This is a warning", - "I'm warning you", - ) + } + + testProviderMetaValue := testNewDynamicValue(t, testProviderMetaType, map[string]tftypes.Value{ + "test_provider_meta_attribute": tftypes.NewValue(tftypes.String, "test-provider-meta-value"), + }) + + testProviderMetaSchema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_provider_meta_attribute": { + Optional: true, + Type: types.StringType, }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - expectedDiags: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "This is a warning", - Detail: "I'm warning you", - Attribute: tftypes.NewAttributePath().WithAttributeName("favorite_colors").WithElementKeyInt(0), + }, + } + + type testProviderMetaData struct { + TestProviderMetaAttribute types.String `tfsdk:"test_provider_meta_attribute"` + } + + testCases := map[string]struct { + server *Server + request *tfprotov6.ApplyResourceChangeRequest + expectedError error + expectedResponse *tfprotov6.ApplyResourceChangeResponse + }{ + "create-request-config": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-config-value" { + resp.Diagnostics.AddError("Unexpected req.Config Value", "Got: "+data.TestRequired.Value) + } + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, nil + }, + }, }, }, - }, - "one_update": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - plannedState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, nil), - }), - resource: "test_one", - action: "update", - resourceType: testServeResourceTypeOneType, - update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }) + PriorState: &testEmptyDynamicValue, + TypeName: "test_resource", }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - }, - "one_update_diags": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - plannedState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, nil), - }), - resource: "test_one", - action: "update", - resourceType: testServeResourceTypeOneType, - update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }) - resp.Diagnostics.AddAttributeWarning( - tftypes.NewAttributePath().WithAttributeName("name"), - "I'm warning you...", - "This is a warning!", - ) + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: &testEmptyDynamicValue, }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - expectedDiags: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "I'm warning you...", - Detail: "This is a warning!", - Attribute: tftypes.NewAttributePath().WithAttributeName("name"), + }, + "create-request-plannedstate": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if data.TestComputed.Value != "test-plannedstate-value" { + resp.Diagnostics.AddError("Unexpected req.Plan Value", "Got: "+data.TestComputed.Value) + } + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, nil + }, + }, }, }, - }, - "one_update_diags_error": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - plannedState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, nil), - }), - resource: "test_one", - action: "update", - resourceType: testServeResourceTypeOneType, - update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }) - resp.Diagnostics.AddAttributeError( - tftypes.NewAttributePath().WithAttributeName("name"), - "Oops!", - "This is an error! Don't update the state!", - ) + PriorState: &testEmptyDynamicValue, + TypeName: "test_resource", }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - expectedDiags: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Oops!", - Detail: "This is an error! Don't update the state!", - Attribute: tftypes.NewAttributePath().WithAttributeName("name"), - }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: &testEmptyDynamicValue, }, }, - "one_delete": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - resource: "test_one", - action: "delete", - resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - // Removing the state prior to the framework should not generate errors - resp.State.RemoveResource(ctx) + "create-request-providermeta": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.ProviderWithProviderMeta{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("Unexpected req.ProviderMeta Value", "Got: "+data.TestProviderMetaAttribute.Value) + } + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, nil + }, + }, + GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testProviderMetaSchema, nil + }, + }, + }, }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil), - }, - "one_delete_diags": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - resource: "test_one", - action: "delete", - resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - // Removing the state prior to the framework should not generate errors - resp.State.RemoveResource(ctx) - resp.Diagnostics.AddAttributeWarning( - tftypes.NewAttributePath().WithAttributeName("created_timestamp"), - "This is a warning", - "just a warning diagnostic, no behavior changes", - ) + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + PriorState: &testEmptyDynamicValue, + ProviderMeta: testProviderMetaValue, + TypeName: "test_resource", }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil), - expectedDiags: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "This is a warning", - Detail: "just a warning diagnostic, no behavior changes", - Attribute: tftypes.NewAttributePath().WithAttributeName("created_timestamp"), - }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + // Intentionally empty, Create implementation does not call resp.State.Set() + NewState: &testEmptyDynamicValue, }, }, - "one_delete_diags_error": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - resource: "test_one", - action: "delete", - resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.Diagnostics.AddError( - "This is an error", - "Something went wrong, keep the old state around", - ) + "create-response-diagnostics": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, nil + }, + }, + }, }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - expectedDiags: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "This is an error", - Detail: "Something went wrong, keep the old state around", + PriorState: &testEmptyDynamicValue, + TypeName: "test_resource", + }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "warning summary", + Detail: "warning detail", + }, + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "error summary", + Detail: "error detail", + }, }, + NewState: &testEmptyDynamicValue, }, }, - "one_delete_automatic_removeresource": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + "create-response-newstate": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update") + }, + }, nil + }, + }, + }, nil + }, + }, + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - resource: "test_one", - action: "delete", - resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - // The framework should automatically call resp.State.RemoveResource() + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), + }), + PriorState: &testEmptyDynamicValue, + TypeName: "test_resource", }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil), - }, - "one_delete_diags_warning_automatic_removeresource": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - resource: "test_one", - action: "delete", - resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - // The framework should automatically call resp.State.RemoveResource() - resp.Diagnostics.AddAttributeWarning( - tftypes.NewAttributePath().WithAttributeName("created_timestamp"), - "This is a warning", - "just a warning diagnostic, no behavior changes", - ) }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil), - expectedDiags: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityWarning, - Summary: "This is a warning", - Detail: "just a warning diagnostic, no behavior changes", - Attribute: tftypes.NewAttributePath().WithAttributeName("created_timestamp"), + }, + "delete-request-priorstate": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-priorstate-value" { + resp.Diagnostics.AddError("Unexpected req.State Value", "Got: "+data.TestRequired.Value) + } + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, nil + }, + }, + }, nil + }, + }, }, }, - }, - "one_delete_diags_error_no_automatic_removeresource": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + request: &tfprotov6.ApplyResourceChangeRequest{ + PlannedState: &testEmptyDynamicValue, + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - resource: "test_one", - action: "delete", - resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - // The framework should NOT automatically call resp.State.RemoveResource() - resp.Diagnostics.AddError( - "This is an error", - "Something went wrong, keep the old state around", - ) + TypeName: "test_resource", }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - expectedDiags: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "This is an error", - Detail: "Something went wrong, keep the old state around", - }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + NewState: &testEmptyDynamicValue, }, }, - "two_create": { - plannedState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, tftypes.UnknownValue), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, nil), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, nil), - }), - resource: "test_two", - action: "create", - resourceType: testServeResourceTypeTwoType, - create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + "delete-request-providermeta": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.ProviderWithProviderMeta{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("Unexpected req.ProviderMeta Value", "Got: "+data.TestProviderMetaAttribute.Value) + } + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, nil + }, + }, + }, nil }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 123), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "stringvalue"), - }), - }), - }) - }, - expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testProviderMetaSchema, nil + }, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + PlannedState: &testEmptyDynamicValue, + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + ProviderMeta: testProviderMetaValue, + TypeName: "test_resource", + }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + NewState: &testEmptyDynamicValue, + }, + }, + "delete-response-diagnostics": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, nil + }, + }, + }, nil }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 123), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), + }, + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + PlannedState: &testEmptyDynamicValue, + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, + TypeName: "test_resource", + }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "warning summary", + Detail: "warning detail", + }, + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "error summary", + Detail: "error detail", }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "stringvalue"), - }), + }, + NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), }), - }), + }, }, - "two_update": { - priorState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + "delete-response-newstate": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + // Intentionally empty, should call resp.State.RemoveResource() automatically. + }, + UpdateMethod: func(_ context.Context, _ tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, nil + }, + }, + }, nil }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 123), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + PlannedState: &testEmptyDynamicValue, + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), }), - }), - plannedState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + TypeName: "test_resource", + }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + NewState: &testEmptyDynamicValue, + }, + }, + "update-request-config": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-new-value" { + resp.Diagnostics.AddError("Unexpected req.Config Value", "Got: "+data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, nil }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, false), - "required_number": tftypes.NewValue(tftypes.Number, 456), - "required_string": tftypes.NewValue(tftypes.String, "newvalue"), - }), + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), }), - }), - config: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, false), - "required_number": tftypes.NewValue(tftypes.Number, 456), - "required_string": tftypes.NewValue(tftypes.String, "newvalue"), - }), + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - }), - resource: "test_two", - action: "update", - resourceType: testServeResourceTypeTwoType, - update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, false), - "required_number": tftypes.NewValue(tftypes.Number, 456), - "required_string": tftypes.NewValue(tftypes.String, "newvalue"), - }), - }), - }) + TypeName: "test_resource", }, - expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, false), - "required_number": tftypes.NewValue(tftypes.Number, 456), - "required_string": tftypes.NewValue(tftypes.String, "newvalue"), - }), - }), - }), + }, }, - "two_delete": { - priorState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + "update-request-plannedstate": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if data.TestComputed.Value != "test-plannedstate-value" { + resp.Diagnostics.AddError("Unexpected req.Plan Value", "Got: "+data.TestComputed.Value) + } + }, + }, nil + }, + }, + }, nil }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), - }), - }), - resource: "test_two", - action: "delete", - resourceType: testServeResourceTypeTwoType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, nil) + }, }, - expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, nil), - }, - "one_meta_create": { - plannedState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, nil), - }), - providerMeta: tftypes.NewValue(testServeProviderMetaType, map[string]tftypes.Value{ - "foo": tftypes.NewValue(tftypes.String, "my provider_meta value"), - }), - resource: "test_one", - action: "create", - resourceType: testServeResourceTypeOneType, - create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }) + TypeName: "test_resource", }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), + }, }, - "one_meta_update": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + "update-request-priorstate": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if data.TestRequired.Value != "test-old-value" { + resp.Diagnostics.AddError("Unexpected req.State Value", "Got: "+data.TestRequired.Value) + } + }, + }, nil + }, + }, + }, nil + }, + }, + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - plannedState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-config-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, nil), - }), - providerMeta: tftypes.NewValue(testServeProviderMetaType, map[string]tftypes.Value{ - "foo": tftypes.NewValue(tftypes.String, "my provider_meta value"), - }), - resource: "test_one", - action: "update", - resourceType: testServeResourceTypeOneType, - update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }) + TypeName: "test_resource", }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "orange"), - tftypes.NewValue(tftypes.String, "yellow"), - }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - }, - "one_meta_delete": { - priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), - "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - "created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"), - }), - providerMeta: tftypes.NewValue(testServeProviderMetaType, map[string]tftypes.Value{ - "foo": tftypes.NewValue(tftypes.String, "my provider_meta value"), - }), - resource: "test_one", - action: "delete", - resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, nil) }, - expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil), }, - "two_meta_create": { - plannedState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, tftypes.UnknownValue), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, tftypes.UnknownValue), - }), - config: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, nil), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, nil), - }), - providerMeta: tftypes.NewValue(testServeProviderMetaType, map[string]tftypes.Value{ - "foo": tftypes.NewValue(tftypes.String, "my provider_meta value"), - }), - resource: "test_two", - action: "create", - resourceType: testServeResourceTypeTwoType, - create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + "update-request-providermeta": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.ProviderWithProviderMeta{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testProviderMetaData + + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &data)...) + + if data.TestProviderMetaAttribute.Value != "test-provider-meta-value" { + resp.Diagnostics.AddError("Unexpected req.ProviderMeta Value", "Got: "+data.TestProviderMetaAttribute.Value) + } + }, + }, nil + }, + }, + }, nil }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 123), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), - }), - }) - }, - expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + GetMetaSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testProviderMetaSchema, nil }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 123), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), }), - }), - }, - "two_meta_update": { - priorState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 123), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - }), - plannedState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), + ProviderMeta: testProviderMetaValue, + TypeName: "test_resource", + }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + // Intentionally old, Update implementation does not call resp.State.Set() + NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, + }, + }, + "update-response-diagnostics": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + resp.Diagnostics.AddWarning("warning summary", "warning detail") + resp.Diagnostics.AddError("error summary", "error detail") + }, + }, nil + }, + }, + }, nil }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), - }), - }), - config: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), }), - }), - providerMeta: tftypes.NewValue(testServeProviderMetaType, map[string]tftypes.Value{ - "foo": tftypes.NewValue(tftypes.String, "my provider_meta value"), - }), - resource: "test_two", - action: "update", - resourceType: testServeResourceTypeTwoType, - update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), - }), - }) - }, - expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, + TypeName: "test_resource", + }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityWarning, + Summary: "warning summary", + Detail: "warning detail", + }, + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "error summary", + Detail: "error detail", }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), + }, + NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), }), - }), + }, }, - "two_meta_delete": { - priorState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "test-instance"), - "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 1234), - "boot": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, - "size_gb": tftypes.Number, - "boot": tftypes.Bool, + "update-response-newstate": { + server: &Server{ + FrameworkServer: fwserver.Server{ + Provider: &testprovider.Provider{ + GetResourcesMethod: func(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "test_resource": &testprovider.ResourceType{ + GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return testSchema, nil + }, + NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Create") + }, + DeleteMethod: func(_ context.Context, _ tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Update, Got: Delete") + }, + UpdateMethod: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + var data testSchemaData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + }, + }, nil + }, + }, + }, nil }, - }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "my-other-disk"), - "size_gb": tftypes.NewValue(tftypes.Number, 2345), - "boot": tftypes.NewValue(tftypes.Bool, false), - }), - }), - "list_nested_blocks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, }, - }}, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "required_bool": tftypes.Bool, - "required_number": tftypes.Number, - "required_string": tftypes.String, - }, - }, map[string]tftypes.Value{ - "required_bool": tftypes.NewValue(tftypes.Bool, true), - "required_number": tftypes.NewValue(tftypes.Number, 123), - "required_string": tftypes.NewValue(tftypes.String, "statevalue"), - }), + }, + }, + request: &tfprotov6.ApplyResourceChangeRequest{ + Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + }), + PriorState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + }), + TypeName: "test_resource", + }, + expectedResponse: &tfprotov6.ApplyResourceChangeResponse{ + NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"), + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), }), - }), - providerMeta: tftypes.NewValue(testServeProviderMetaType, map[string]tftypes.Value{ - "foo": tftypes.NewValue(tftypes.String, "my provider_meta value"), - }), - resource: "test_two", - action: "delete", - resourceType: testServeResourceTypeTwoType, - destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { - resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, nil) }, - expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, nil), }, } - for name, tc := range tests { - name, tc := name, tc + for name, testCase := range testCases { + name, testCase := name, testCase t.Run(name, func(t *testing.T) { t.Parallel() - s := &testServeProvider{ - createFunc: tc.create, - updateFunc: tc.update, - deleteFunc: tc.destroy, - } - testServer := &Server{ - FrameworkServer: fwserver.Server{ - Provider: s, - }, - } - var pmSchema tfsdk.Schema - if tc.providerMeta.Type() != nil { - testServer.FrameworkServer.Provider = &testServeProviderWithMetaSchema{s} - schema, diags := testServer.FrameworkServer.ProviderMetaSchema(context.Background()) - if len(diags) > 0 { - t.Errorf("Unexpected diags: %+v", diags) - return - } - pmSchema = *schema - } + got, err := testCase.server.ApplyResourceChange(context.Background(), testCase.request) - rt, diags := testServer.FrameworkServer.ResourceType(context.Background(), tc.resource) - if len(diags) > 0 { - t.Errorf("Unexpected diags: %+v", diags) - return - } - schema, diags := rt.GetSchema(context.Background()) - if len(diags) > 0 { - t.Errorf("Unexpected diags: %+v", diags) - return + if diff := cmp.Diff(testCase.expectedError, err); diff != "" { + t.Errorf("unexpected error difference: %s", diff) } - priorState, err := tfprotov6.NewDynamicValue(tc.resourceType, tc.priorState) - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - plannedState, err := tfprotov6.NewDynamicValue(tc.resourceType, tc.plannedState) - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - config, err := tfprotov6.NewDynamicValue(tc.resourceType, tc.config) - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - req := &tfprotov6.ApplyResourceChangeRequest{ - TypeName: tc.resource, - PlannedPrivate: tc.plannedPrivate, - PriorState: &priorState, - PlannedState: &plannedState, - Config: &config, - } - if tc.providerMeta.Type() != nil { - providerMeta, err := tfprotov6.NewDynamicValue(testServeProviderMetaType, tc.providerMeta) - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - req.ProviderMeta = &providerMeta - } - got, err := testServer.ApplyResourceChange(context.Background(), req) - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - if diff := cmp.Diff(got.Diagnostics, tc.expectedDiags); diff != "" { - t.Errorf("Unexpected diff in diagnostics (+wanted, -got): %s", diff) - } - if s.applyResourceChangeCalledResourceType != tc.resource { - t.Errorf("Called wrong resource. Expected to call %q, actually called %q", tc.resource, s.applyResourceChangeCalledResourceType) - return - } - if s.applyResourceChangeCalledAction != tc.action { - t.Errorf("Called wrong action. Expected to call %q, actually called %q", tc.action, s.applyResourceChangeCalledAction) - return - } - if tc.priorState.Type() != nil { - if diff := cmp.Diff(s.applyResourceChangePriorStateValue, tc.priorState); diff != "" { - t.Errorf("Unexpected diff in prior state (+wanted, -got): %s", diff) - return - } - if diff := cmp.Diff(s.applyResourceChangePriorStateSchema, schema); diff != "" { - t.Errorf("Unexpected diff in prior state schema (+wanted, -got): %s", diff) - return - } - } - if tc.plannedState.Type() != nil { - if diff := cmp.Diff(s.applyResourceChangePlannedStateValue, tc.plannedState); diff != "" { - t.Errorf("Unexpected diff in planned state (+wanted, -got): %s", diff) - return - } - if diff := cmp.Diff(s.applyResourceChangePlannedStateSchema, schema); diff != "" { - t.Errorf("Unexpected diff in planned state schema (+wanted, -got): %s", diff) - return - } - } - if tc.config.Type() != nil { - if diff := cmp.Diff(s.applyResourceChangeConfigValue, tc.config); diff != "" { - t.Errorf("Unexpected diff in config (+wanted, -got): %s", diff) - return - } - if diff := cmp.Diff(s.applyResourceChangeConfigSchema, schema); diff != "" { - t.Errorf("Unexpected diff in config schema (+wanted, -got): %s", diff) - return - } - } - if tc.providerMeta.Type() != nil { - if diff := cmp.Diff(s.applyResourceChangeProviderMetaValue, tc.providerMeta); diff != "" { - t.Errorf("Unexpected diff in provider meta (+wanted, -got): %s", diff) - return - } - if diff := cmp.Diff(s.applyResourceChangeProviderMetaSchema, pmSchema); diff != "" { - t.Errorf("Unexpected diff in provider meta schema (+wanted, -got): %s", diff) - return - } - } - gotNewState, err := got.NewState.Unmarshal(tc.resourceType) - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - if diff := cmp.Diff(gotNewState, tc.expectedNewState); diff != "" { - t.Errorf("Unexpected diff in new state (+wanted, -got): %s", diff) - return - } - if string(got.Private) != string(tc.expectedPrivate) { - t.Errorf("Expected private to be %q, got %q", tc.expectedPrivate, got.Private) - return + if diff := cmp.Diff(testCase.expectedResponse, got); diff != "" { + t.Errorf("unexpected response difference: %s", diff) } }) } diff --git a/internal/testing/testprovider/resource.go b/internal/testing/testprovider/resource.go index 3064dfbf0..6cbc7f293 100644 --- a/internal/testing/testprovider/resource.go +++ b/internal/testing/testprovider/resource.go @@ -23,7 +23,7 @@ func (r *Resource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, return } - r.Create(ctx, req, resp) + r.CreateMethod(ctx, req, resp) } // Delete satisfies the tfsdk.Resource interface. @@ -32,7 +32,7 @@ func (r *Resource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, return } - r.Delete(ctx, req, resp) + r.DeleteMethod(ctx, req, resp) } // Read satisfies the tfsdk.Resource interface. @@ -41,7 +41,7 @@ func (r *Resource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp return } - r.Read(ctx, req, resp) + r.ReadMethod(ctx, req, resp) } // Update satisfies the tfsdk.Resource interface. @@ -50,5 +50,5 @@ func (r *Resource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, return } - r.Update(ctx, req, resp) + r.UpdateMethod(ctx, req, resp) }