// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package plugin import ( "bytes" "fmt" "testing" "github.com/google/go-cmp/cmp" "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/plugin/mock_proto" "github.com/opentofu/opentofu/internal/providers" "github.com/opentofu/opentofu/internal/tfdiags" proto "github.com/opentofu/opentofu/internal/tfplugin5" ) var _ providers.Interface = (*GRPCProvider)(nil) func mockProviderClient(t *testing.T) *mockproto.MockProviderClient { ctrl := gomock.NewController(t) client := mockproto.NewMockProviderClient(ctrl) // we always need a GetSchema method client.EXPECT().GetSchema( 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": &proto.Schema{ Version: 1, Block: &proto.Schema_Block{ Attributes: []*proto.Schema_Attribute{ { Name: "attr", Type: []byte(`"string"`), Required: true, }, }, }, }, }, DataSourceSchemas: map[string]*proto.Schema{ "data": &proto.Schema{ 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() 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().GetSchema( gomock.Any(), gomock.Any(), gomock.Any(), ).Return(&proto.GetProviderSchema_Response{}, fmt.Errorf("test error")) p := &GRPCProvider{ client: client, } resp := p.GetProviderSchema() 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().GetSchema( 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() 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() 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().GetSchema( 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() 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() checkDiags(t, resp.Diagnostics) if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) { t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version)) } } // 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().GetSchema( 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() checkDiagsHasError(t, resp.Diagnostics) } func TestGRPCProvider_PrepareProviderConfig(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().PrepareProviderConfig( gomock.Any(), gomock.Any(), ).Return(&proto.PrepareProviderConfig_Response{}, nil) cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"}) resp := p.ValidateProviderConfig(providers.ValidateProviderConfigRequest{Config: cfg}) checkDiags(t, resp.Diagnostics) } func TestGRPCProvider_ValidateResourceConfig(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ValidateResourceTypeConfig( gomock.Any(), gomock.Any(), ).Return(&proto.ValidateResourceTypeConfig_Response{}, nil) cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"}) resp := p.ValidateResourceConfig(providers.ValidateResourceConfigRequest{ TypeName: "resource", Config: cfg, }) checkDiags(t, resp.Diagnostics) } func TestGRPCProvider_ValidateDataSourceConfig(t *testing.T) { client := mockProviderClient(t) p := &GRPCProvider{ client: client, } client.EXPECT().ValidateDataSourceConfig( gomock.Any(), gomock.Any(), ).Return(&proto.ValidateDataSourceConfig_Response{}, nil) cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"}) resp := p.ValidateDataResourceConfig(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(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(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(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().Configure( gomock.Any(), gomock.Any(), ).Return(&proto.Configure_Response{}, nil) resp := p.ConfigureProvider(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().Stop( gomock.Any(), gomock.Any(), ).Return(&proto.Stop_Response{}, nil) err := p.Stop() 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(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(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(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(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_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(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(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(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(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(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(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(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(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) } }