// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package plugin6 import ( "bytes" "fmt" "testing" "github.com/davecgh/go-spew/spew" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/zclconf/go-cty/cty" "go.uber.org/mock/gomock" "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/configs/hcl2shim" mockproto "github.com/opentofu/opentofu/internal/plugin6/mock_proto" "github.com/opentofu/opentofu/internal/providers" "github.com/opentofu/opentofu/internal/tfdiags" proto "github.com/opentofu/opentofu/internal/tfplugin6" ) var _ providers.Interface = (*GRPCProvider)(nil) var ( equateEmpty = cmpopts.EquateEmpty() typeComparer = cmp.Comparer(cty.Type.Equals) valueComparer = cmp.Comparer(cty.Value.RawEquals) ) func mockProviderClient(t *testing.T) *mockproto.MockProviderClient { ctrl := gomock.NewController(t) client := mockproto.NewMockProviderClient(ctrl) // we always need a GetSchema method client.EXPECT().GetProviderSchema( gomock.Any(), gomock.Any(), gomock.Any(), ).Return(providerProtoSchema(), nil) return client } func checkDiags(t *testing.T, d tfdiags.Diagnostics) { t.Helper() if d.HasErrors() { t.Fatal(d.Err()) } } // checkDiagsHasError ensures error diagnostics are present or fails the test. func checkDiagsHasError(t *testing.T, d tfdiags.Diagnostics) { t.Helper() if !d.HasErrors() { t.Fatal("expected error diagnostics") } } func providerProtoSchema() *proto.GetProviderSchema_Response { return &proto.GetProviderSchema_Response{ Provider: &proto.Schema{ Block: &proto.Schema_Block{ Attributes: []*proto.Schema_Attribute{ { Name: "attr", Type: []byte(`"string"`), Required: true, }, }, }, }, ResourceSchemas: map[string]*proto.Schema{ "resource": { Version: 1, Block: &proto.Schema_Block{ Attributes: []*proto.Schema_Attribute{ { Name: "attr", Type: []byte(`"string"`), Required: true, }, }, }, }, }, DataSourceSchemas: map[string]*proto.Schema{ "data": { Version: 1, Block: &proto.Schema_Block{ Attributes: []*proto.Schema_Attribute{ { Name: "attr", Type: []byte(`"string"`), Required: true, }, }, }, }, }, Functions: map[string]*proto.Function{ "fn": &proto.Function{ Parameters: []*proto.Function_Parameter{{ Name: "par_a", Type: []byte(`"string"`), AllowNullValue: false, AllowUnknownValues: false, }}, VariadicParameter: &proto.Function_Parameter{ Name: "par_var", Type: []byte(`"string"`), AllowNullValue: true, AllowUnknownValues: false, }, Return: &proto.Function_Return{ Type: []byte(`"string"`), }, }, }, } } func TestGRPCProvider_GetSchema(t *testing.T) { p := &GRPCProvider{ client: mockProviderClient(t), } resp := p.GetProviderSchema(t.Context()) checkDiags(t, resp.Diagnostics) } // Ensure that gRPC errors are returned early. // Reference: https://github.com/hashicorp/terraform/issues/31047 func TestGRPCProvider_GetSchema_GRPCError(t *testing.T) { ctrl := gomock.NewController(t) client := mockproto.NewMockProviderClient(ctrl) client.EXPECT().GetProviderSchema( gomock.Any(), gomock.Any(), gomock.Any(), ).Return(&proto.GetProviderSchema_Response{}, fmt.Errorf("test error")) p := &GRPCProvider{ client: client, } resp := p.GetProviderSchema(t.Context()) checkDiagsHasError(t, resp.Diagnostics) } // Ensure that provider error diagnostics are returned early. // Reference: https://github.com/hashicorp/terraform/issues/31047 func TestGRPCProvider_GetSchema_ResponseErrorDiagnostic(t *testing.T) { ctrl := gomock.NewController(t) client := mockproto.NewMockProviderClient(ctrl) client.EXPECT().GetProviderSchema( gomock.Any(), gomock.Any(), gomock.Any(), ).Return(&proto.GetProviderSchema_Response{ Diagnostics: []*proto.Diagnostic{ { Severity: proto.Diagnostic_ERROR, Summary: "error summary", Detail: "error detail", }, }, // Trigger potential panics Provider: &proto.Schema{}, }, nil) p := &GRPCProvider{ client: client, } resp := p.GetProviderSchema(t.Context()) checkDiagsHasError(t, resp.Diagnostics) } func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) { ctrl := gomock.NewController(t) client := mockproto.NewMockProviderClient(ctrl) // The SchemaCache is global and is saved between test runs providers.SchemaCache = providers.NewMockSchemaCache() providerAddr := addrs.Provider{ Namespace: "namespace", Type: "type", } mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}} client.EXPECT().GetProviderSchema( gomock.Any(), gomock.Any(), gomock.Any(), ).Times(1).Return(&proto.GetProviderSchema_Response{ Provider: mockedProviderResponse, ServerCapabilities: &proto.ServerCapabilities{GetProviderSchemaOptional: true}, }, nil) // Run GetProviderTwice, expect GetSchema to be called once // Re-initialize the provider before each run to avoid usage of the local cache p := &GRPCProvider{ client: client, Addr: providerAddr, } resp := p.GetProviderSchema(t.Context()) checkDiags(t, resp.Diagnostics) if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) { t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version)) } p = &GRPCProvider{ client: client, Addr: providerAddr, } resp = p.GetProviderSchema(t.Context()) checkDiags(t, resp.Diagnostics) if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) { t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version)) } } func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) { ctrl := gomock.NewController(t) client := mockproto.NewMockProviderClient(ctrl) // The SchemaCache is global and is saved between test runs providers.SchemaCache = providers.NewMockSchemaCache() providerAddr := addrs.Provider{ Namespace: "namespace", Type: "type", } mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}} client.EXPECT().GetProviderSchema( gomock.Any(), gomock.Any(), gomock.Any(), ).Times(2).Return(&proto.GetProviderSchema_Response{ Provider: mockedProviderResponse, ServerCapabilities: &proto.ServerCapabilities{GetProviderSchemaOptional: false}, }, nil) // Run GetProviderTwice, expect GetSchema to be called once // Re-initialize the provider before each run to avoid usage of the local cache p := &GRPCProvider{ client: client, Addr: providerAddr, } resp := p.GetProviderSchema(t.Context()) checkDiags(t, resp.Diagnostics) if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) { t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version)) } p = &GRPCProvider{ client: client, Addr: providerAddr, } resp = p.GetProviderSchema(t.Context()) checkDiags(t, resp.Diagnostics) if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) { t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version)) } } func TestGRPCProvider_PrepareProviderConfig(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ValidateProviderConfig( gomock.Any(), gomock.Any(), ).Return(&proto.ValidateProviderConfig_Response{}, nil) cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"}) resp := p.ValidateProviderConfig(t.Context(), providers.ValidateProviderConfigRequest{Config: cfg}) checkDiags(t, resp.Diagnostics) } func TestGRPCProvider_ValidateResourceConfig(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ValidateResourceConfig( gomock.Any(), gomock.Any(), ).Return(&proto.ValidateResourceConfig_Response{}, nil) cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"}) resp := p.ValidateResourceConfig(t.Context(), providers.ValidateResourceConfigRequest{ TypeName: "resource", Config: cfg, }) checkDiags(t, resp.Diagnostics) } func TestGRPCProvider_ValidateDataResourceConfig(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ValidateDataResourceConfig( gomock.Any(), gomock.Any(), ).Return(&proto.ValidateDataResourceConfig_Response{}, nil) cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"}) resp := p.ValidateDataResourceConfig(t.Context(), providers.ValidateDataResourceConfigRequest{ TypeName: "data", Config: cfg, }) checkDiags(t, resp.Diagnostics) } func TestGRPCProvider_UpgradeResourceState(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().UpgradeResourceState( gomock.Any(), gomock.Any(), ).Return(&proto.UpgradeResourceState_Response{ UpgradedState: &proto.DynamicValue{ Msgpack: []byte("\x81\xa4attr\xa3bar"), }, }, nil) resp := p.UpgradeResourceState(t.Context(), providers.UpgradeResourceStateRequest{ TypeName: "resource", Version: 0, RawStateJSON: []byte(`{"old_attr":"bar"}`), }) checkDiags(t, resp.Diagnostics) expected := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_UpgradeResourceStateJSON(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().UpgradeResourceState( gomock.Any(), gomock.Any(), ).Return(&proto.UpgradeResourceState_Response{ UpgradedState: &proto.DynamicValue{ Json: []byte(`{"attr":"bar"}`), }, }, nil) resp := p.UpgradeResourceState(t.Context(), providers.UpgradeResourceStateRequest{ TypeName: "resource", Version: 0, RawStateJSON: []byte(`{"old_attr":"bar"}`), }) checkDiags(t, resp.Diagnostics) expected := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_MoveResourceState(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().MoveResourceState( gomock.Any(), gomock.Any(), ).Return(&proto.MoveResourceState_Response{ TargetState: &proto.DynamicValue{ Msgpack: []byte("\x81\xa4attr\xa3bar"), }, TargetPrivate: []byte(`{"meta": "data"}`), }, nil) resp := p.MoveResourceState(t.Context(), providers.MoveResourceStateRequest{ SourceTypeName: "resource_old", SourceSchemaVersion: 0, TargetTypeName: "resource", }) checkDiags(t, resp.Diagnostics) expectedState := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) expectedPrivate := []byte(`{"meta": "data"}`) if !cmp.Equal(expectedState, resp.TargetState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expectedState, resp.TargetState, typeComparer, valueComparer, equateEmpty)) } if !bytes.Equal(expectedPrivate, resp.TargetPrivate) { t.Fatalf("expected %q, got %q", expectedPrivate, resp.TargetPrivate) } } func TestGRPCProvider_Configure(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ConfigureProvider( gomock.Any(), gomock.Any(), ).Return(&proto.ConfigureProvider_Response{}, nil) resp := p.ConfigureProvider(t.Context(), providers.ConfigureProviderRequest{ Config: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), }) checkDiags(t, resp.Diagnostics) } func TestGRPCProvider_Stop(t *testing.T) { ctrl := gomock.NewController(t) client := mockproto.NewMockProviderClient(ctrl) p := &GRPCProvider{ client: client, } client.EXPECT().StopProvider( gomock.Any(), gomock.Any(), ).Return(&proto.StopProvider_Response{}, nil) err := p.Stop(t.Context()) if err != nil { t.Fatal(err) } } func TestGRPCProvider_ReadResource(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ReadResource( gomock.Any(), gomock.Any(), ).Return(&proto.ReadResource_Response{ NewState: &proto.DynamicValue{ Msgpack: []byte("\x81\xa4attr\xa3bar"), }, }, nil) resp := p.ReadResource(t.Context(), providers.ReadResourceRequest{ TypeName: "resource", PriorState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), }) checkDiags(t, resp.Diagnostics) expected := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_ReadResourceJSON(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ReadResource( gomock.Any(), gomock.Any(), ).Return(&proto.ReadResource_Response{ NewState: &proto.DynamicValue{ Json: []byte(`{"attr":"bar"}`), }, }, nil) resp := p.ReadResource(t.Context(), providers.ReadResourceRequest{ TypeName: "resource", PriorState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), }) checkDiags(t, resp.Diagnostics) expected := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_ReadEmptyJSON(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ReadResource( gomock.Any(), gomock.Any(), ).Return(&proto.ReadResource_Response{ NewState: &proto.DynamicValue{ Json: []byte(``), }, }, nil) obj := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }) resp := p.ReadResource(t.Context(), providers.ReadResourceRequest{ TypeName: "resource", PriorState: obj, }) checkDiags(t, resp.Diagnostics) expected := cty.NullVal(obj.Type()) if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_PlanResourceChange(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } expectedPrivate := []byte(`{"meta": "data"}`) client.EXPECT().PlanResourceChange( gomock.Any(), gomock.Any(), ).Return(&proto.PlanResourceChange_Response{ PlannedState: &proto.DynamicValue{ Msgpack: []byte("\x81\xa4attr\xa3bar"), }, RequiresReplace: []*proto.AttributePath{ { Steps: []*proto.AttributePath_Step{ { Selector: &proto.AttributePath_Step_AttributeName{ AttributeName: "attr", }, }, }, }, }, PlannedPrivate: expectedPrivate, }, nil) resp := p.PlanResourceChange(t.Context(), providers.PlanResourceChangeRequest{ TypeName: "resource", PriorState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), ProposedNewState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), Config: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), }) checkDiags(t, resp.Diagnostics) expectedState := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty)) } expectedReplace := `[]cty.Path{cty.Path{cty.GetAttrStep{Name:"attr"}}}` replace := fmt.Sprintf("%#v", resp.RequiresReplace) if expectedReplace != replace { t.Fatalf("expected %q, got %q", expectedReplace, replace) } if !bytes.Equal(expectedPrivate, resp.PlannedPrivate) { t.Fatalf("expected %q, got %q", expectedPrivate, resp.PlannedPrivate) } } func TestGRPCProvider_PlanResourceChange_deferred(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().PlanResourceChange( gomock.Any(), gomock.Any(), ).Return(&proto.PlanResourceChange_Response{ PlannedState: &proto.DynamicValue{ Msgpack: []byte("\x81\xa4attr\xa3bar"), }, Deferred: &proto.Deferred{ Reason: proto.Deferred_PROVIDER_CONFIG_UNKNOWN, }, }, nil) resp := p.PlanResourceChange(t.Context(), providers.PlanResourceChangeRequest{ TypeName: "resource", PriorState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), ProposedNewState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), Config: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), }) if len(resp.Diagnostics) != 1 { t.Fatal("wrong number of diagnostics; want one\n" + spew.Sdump(resp.Diagnostics)) } desc := resp.Diagnostics[0].Description() if got, want := desc.Summary, `Provider configuration is incomplete`; got != want { t.Errorf("wrong error summary\ngot: %s\nwant: %s", got, want) } if got, want := desc.Detail, `The provider was unable to work with this resource because the associated provider configuration makes use of values from other resources that will not be known until after apply.`; got != want { t.Errorf("wrong error detail\ngot: %s\nwant: %s", got, want) } if !providers.IsDeferralDiagnostic(resp.Diagnostics[0]) { t.Errorf("diagnostic is not marked as being a \"deferral diagnostic\"") } } func TestGRPCProvider_PlanResourceChangeJSON(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } expectedPrivate := []byte(`{"meta": "data"}`) client.EXPECT().PlanResourceChange( gomock.Any(), gomock.Any(), ).Return(&proto.PlanResourceChange_Response{ PlannedState: &proto.DynamicValue{ Json: []byte(`{"attr":"bar"}`), }, RequiresReplace: []*proto.AttributePath{ { Steps: []*proto.AttributePath_Step{ { Selector: &proto.AttributePath_Step_AttributeName{ AttributeName: "attr", }, }, }, }, }, PlannedPrivate: expectedPrivate, }, nil) resp := p.PlanResourceChange(t.Context(), providers.PlanResourceChangeRequest{ TypeName: "resource", PriorState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), ProposedNewState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), Config: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), }) checkDiags(t, resp.Diagnostics) expectedState := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty)) } expectedReplace := `[]cty.Path{cty.Path{cty.GetAttrStep{Name:"attr"}}}` replace := fmt.Sprintf("%#v", resp.RequiresReplace) if expectedReplace != replace { t.Fatalf("expected %q, got %q", expectedReplace, replace) } if !bytes.Equal(expectedPrivate, resp.PlannedPrivate) { t.Fatalf("expected %q, got %q", expectedPrivate, resp.PlannedPrivate) } } func TestGRPCProvider_ApplyResourceChange(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } expectedPrivate := []byte(`{"meta": "data"}`) client.EXPECT().ApplyResourceChange( gomock.Any(), gomock.Any(), ).Return(&proto.ApplyResourceChange_Response{ NewState: &proto.DynamicValue{ Msgpack: []byte("\x81\xa4attr\xa3bar"), }, Private: expectedPrivate, }, nil) resp := p.ApplyResourceChange(t.Context(), providers.ApplyResourceChangeRequest{ TypeName: "resource", PriorState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), PlannedState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), Config: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), PlannedPrivate: expectedPrivate, }) checkDiags(t, resp.Diagnostics) expectedState := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty)) } if !bytes.Equal(expectedPrivate, resp.Private) { t.Fatalf("expected %q, got %q", expectedPrivate, resp.Private) } } func TestGRPCProvider_ApplyResourceChangeJSON(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } expectedPrivate := []byte(`{"meta": "data"}`) client.EXPECT().ApplyResourceChange( gomock.Any(), gomock.Any(), ).Return(&proto.ApplyResourceChange_Response{ NewState: &proto.DynamicValue{ Json: []byte(`{"attr":"bar"}`), }, Private: expectedPrivate, }, nil) resp := p.ApplyResourceChange(t.Context(), providers.ApplyResourceChangeRequest{ TypeName: "resource", PriorState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), PlannedState: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), Config: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), PlannedPrivate: expectedPrivate, }) checkDiags(t, resp.Diagnostics) expectedState := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty)) } if !bytes.Equal(expectedPrivate, resp.Private) { t.Fatalf("expected %q, got %q", expectedPrivate, resp.Private) } } func TestGRPCProvider_ImportResourceState(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } expectedPrivate := []byte(`{"meta": "data"}`) client.EXPECT().ImportResourceState( gomock.Any(), gomock.Any(), ).Return(&proto.ImportResourceState_Response{ ImportedResources: []*proto.ImportResourceState_ImportedResource{ { TypeName: "resource", State: &proto.DynamicValue{ Msgpack: []byte("\x81\xa4attr\xa3bar"), }, Private: expectedPrivate, }, }, }, nil) resp := p.ImportResourceState(t.Context(), providers.ImportResourceStateRequest{ TypeName: "resource", ID: "foo", }) checkDiags(t, resp.Diagnostics) expectedResource := providers.ImportedResource{ TypeName: "resource", State: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), Private: expectedPrivate, } imported := resp.ImportedResources[0] if !cmp.Equal(expectedResource, imported, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expectedResource, imported, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_ImportResourceStateJSON(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } expectedPrivate := []byte(`{"meta": "data"}`) client.EXPECT().ImportResourceState( gomock.Any(), gomock.Any(), ).Return(&proto.ImportResourceState_Response{ ImportedResources: []*proto.ImportResourceState_ImportedResource{ { TypeName: "resource", State: &proto.DynamicValue{ Json: []byte(`{"attr":"bar"}`), }, Private: expectedPrivate, }, }, }, nil) resp := p.ImportResourceState(t.Context(), providers.ImportResourceStateRequest{ TypeName: "resource", ID: "foo", }) checkDiags(t, resp.Diagnostics) expectedResource := providers.ImportedResource{ TypeName: "resource", State: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }), Private: expectedPrivate, } imported := resp.ImportedResources[0] if !cmp.Equal(expectedResource, imported, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expectedResource, imported, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_ReadDataSource(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ReadDataSource( gomock.Any(), gomock.Any(), ).Return(&proto.ReadDataSource_Response{ State: &proto.DynamicValue{ Msgpack: []byte("\x81\xa4attr\xa3bar"), }, }, nil) resp := p.ReadDataSource(t.Context(), providers.ReadDataSourceRequest{ TypeName: "data", Config: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), }) checkDiags(t, resp.Diagnostics) expected := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expected, resp.State, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_ReadDataSourceJSON(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ReadDataSource( gomock.Any(), gomock.Any(), ).Return(&proto.ReadDataSource_Response{ State: &proto.DynamicValue{ Json: []byte(`{"attr":"bar"}`), }, }, nil) resp := p.ReadDataSource(t.Context(), providers.ReadDataSourceRequest{ TypeName: "data", Config: cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("foo"), }), }) checkDiags(t, resp.Diagnostics) expected := cty.ObjectVal(map[string]cty.Value{ "attr": cty.StringVal("bar"), }) if !cmp.Equal(expected, resp.State, typeComparer, valueComparer, equateEmpty) { t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty)) } } func TestGRPCProvider_CallFunction(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().CallFunction( gomock.Any(), gomock.Any(), ).Return(&proto.CallFunction_Response{ Result: &proto.DynamicValue{Json: []byte(`"foo"`)}, }, nil) resp := p.CallFunction(t.Context(), providers.CallFunctionRequest{ Name: "fn", Arguments: []cty.Value{cty.StringVal("bar"), cty.NilVal}, }) if resp.Error != nil { t.Fatal(resp.Error) } if resp.Result != cty.StringVal("foo") { t.Fatalf("%v", resp.Result) } }