// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package plugin6

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/zclconf/go-cty/cty"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/timestamppb"

	"github.com/hashicorp/terraform/internal/addrs"
	"github.com/hashicorp/terraform/internal/backend"
	"github.com/hashicorp/terraform/internal/configs/configschema"
	"github.com/hashicorp/terraform/internal/configs/hcl2shim"
	"github.com/hashicorp/terraform/internal/plans"
	"github.com/hashicorp/terraform/internal/plugin6/convert"
	mockproto "github.com/hashicorp/terraform/internal/plugin6/mock_proto"
	"github.com/hashicorp/terraform/internal/providers"
	"github.com/hashicorp/terraform/internal/schemarepo"
	"github.com/hashicorp/terraform/internal/tfdiags"
	"github.com/hashicorp/terraform/internal/tfplugin6"
	proto "github.com/hashicorp/terraform/internal/tfplugin6"
)

var _ providers.Interface = (*GRPCProvider)(nil)
var _ providers.StateStoreChunkSizeSetter = (*GRPCProvider)(nil) // Specific to the v6 version of GRPCProvider

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)

	// GetResourceIdentitySchemas is called as part of GetSchema
	client.EXPECT().GetResourceIdentitySchemas(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(providerResourceIdentitySchemas(), nil)

	return client
}

func mockReadStateBytesClient(t *testing.T) *mockproto.MockProvider_ReadStateBytesClient[tfplugin6.ReadStateBytes_Response] {
	ctrl := gomock.NewController(t)
	return mockproto.NewMockProvider_ReadStateBytesClient[tfplugin6.ReadStateBytes_Response](ctrl)
}

func mockWriteStateBytesClient(t *testing.T) *mockproto.MockProvider_WriteStateBytesClient[tfplugin6.WriteStateBytes_RequestChunk, tfplugin6.WriteStateBytes_Response] {
	ctrl := gomock.NewController(t)
	return mockproto.NewMockProvider_WriteStateBytesClient[tfplugin6.WriteStateBytes_RequestChunk, tfplugin6.WriteStateBytes_Response](ctrl)
}

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,
						},
					},
				},
			},
			"list": {
				Version: 1,
				Block: &proto.Schema_Block{
					Attributes: []*proto.Schema_Attribute{
						{
							Name:     "resource_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,
						},
					},
				},
			},
		},
		EphemeralResourceSchemas: map[string]*proto.Schema{
			"ephemeral": &proto.Schema{
				Block: &proto.Schema_Block{
					Attributes: []*proto.Schema_Attribute{
						{
							Name:     "attr",
							Type:     []byte(`"string"`),
							Computed: true,
						},
					},
				},
			},
		},
		ListResourceSchemas: map[string]*proto.Schema{
			"list": &proto.Schema{
				Version: 1,
				Block: &proto.Schema_Block{
					Attributes: []*proto.Schema_Attribute{
						{
							Name:     "filter_attr",
							Type:     []byte(`"string"`),
							Required: true,
						},
					},
				},
			},
		},
		ActionSchemas: map[string]*proto.ActionSchema{
			"action": {
				Schema: &proto.Schema{
					Block: &proto.Schema_Block{
						Version: 1,
						Attributes: []*proto.Schema_Attribute{
							{
								Name: "attr",
								Type: []byte(`"string"`),
							},
						},
					},
				},
			},
		},
		StateStoreSchemas: map[string]*proto.Schema{
			"mock_store": {
				Block: &proto.Schema_Block{
					Version: 1,
					Attributes: []*proto.Schema_Attribute{
						{
							Name: "region",
							Type: []byte(`"string"`),
						},
					},
				},
			},
		},
		ServerCapabilities: &proto.ServerCapabilities{
			GetProviderSchemaOptional: true,
		},
	}
}

func providerResourceIdentitySchemas() *proto.GetResourceIdentitySchemas_Response {
	return &proto.GetResourceIdentitySchemas_Response{
		IdentitySchemas: map[string]*proto.ResourceIdentitySchema{
			"resource": {
				Version: 1,
				IdentityAttributes: []*proto.ResourceIdentitySchema_IdentityAttribute{
					{
						Name:              "id_attr",
						Type:              []byte(`"string"`),
						RequiredForImport: true,
					},
				},
			},
			"list": {
				Version: 1,
				IdentityAttributes: []*proto.ResourceIdentitySchema_IdentityAttribute{
					{
						Name:              "id_attr",
						Type:              []byte(`"string"`),
						RequiredForImport: true,
					},
				},
			},
		},
	}
}

func TestGRPCProvider_GetProviderSchema(t *testing.T) {
	p := &GRPCProvider{
		client: mockProviderClient(t),
	}

	resp := p.GetProviderSchema()
	checkDiags(t, resp.Diagnostics)
}

// ensure that the global schema cache is used when the provider supports
// GetProviderSchemaOptional
func TestGRPCProvider_GetSchema_globalCache(t *testing.T) {
	p := &GRPCProvider{
		Addr:   addrs.ImpliedProviderForUnqualifiedType("test"),
		client: mockProviderClient(t),
	}

	// first call primes the cache
	resp := p.GetProviderSchema()

	// create a new provider instance which does not expect a GetProviderSchemaCall
	p = &GRPCProvider{
		Addr:   addrs.ImpliedProviderForUnqualifiedType("test"),
		client: mockproto.NewMockProviderClient(gomock.NewController(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().GetProviderSchema(
		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)
}

// 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()

	checkDiagsHasError(t, resp.Diagnostics)
}

func TestGRPCProvider_GetSchema_IdentityError(t *testing.T) {
	ctrl := gomock.NewController(t)
	client := mockproto.NewMockProviderClient(ctrl)

	client.EXPECT().GetProviderSchema(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(providerProtoSchema(), nil)

	client.EXPECT().GetResourceIdentitySchemas(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.GetResourceIdentitySchemas_Response{}, fmt.Errorf("test error"))

	p := &GRPCProvider{
		client: client,
	}

	resp := p.GetProviderSchema()

	checkDiagsHasError(t, resp.Diagnostics)
}

func TestGRPCProvider_GetSchema_IdentityUnimplemented(t *testing.T) {
	ctrl := gomock.NewController(t)
	client := mockproto.NewMockProviderClient(ctrl)

	client.EXPECT().GetProviderSchema(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(providerProtoSchema(), nil)

	client.EXPECT().GetResourceIdentitySchemas(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.GetResourceIdentitySchemas_Response{}, status.Error(codes.Unimplemented, "test error"))

	p := &GRPCProvider{
		client: client,
	}

	resp := p.GetProviderSchema()

	checkDiags(t, resp.Diagnostics)
}

func TestGRPCProvider_GetSchema_IdentityErrorDiagnostic(t *testing.T) {
	ctrl := gomock.NewController(t)
	client := mockproto.NewMockProviderClient(ctrl)

	client.EXPECT().GetProviderSchema(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(providerProtoSchema(), nil)

	client.EXPECT().GetResourceIdentitySchemas(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.GetResourceIdentitySchemas_Response{
		Diagnostics: []*proto.Diagnostic{
			{
				Severity: proto.Diagnostic_ERROR,
				Summary:  "error summary",
				Detail:   "error detail",
			},
		},
		IdentitySchemas: map[string]*proto.ResourceIdentitySchema{},
	}, nil)

	p := &GRPCProvider{
		client: client,
	}

	resp := p.GetProviderSchema()

	checkDiagsHasError(t, resp.Diagnostics)
}

func TestGRPCProvider_GetResourceIdentitySchemas(t *testing.T) {
	ctrl := gomock.NewController(t)
	client := mockproto.NewMockProviderClient(ctrl)

	client.EXPECT().GetResourceIdentitySchemas(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(providerResourceIdentitySchemas(), nil)

	p := &GRPCProvider{
		client: client,
	}

	resp := p.GetResourceIdentitySchemas()

	checkDiags(t, resp.Diagnostics)
}

func TestGRPCProvider_GetResourceIdentitySchemas_Unimplemented(t *testing.T) {
	ctrl := gomock.NewController(t)
	client := mockproto.NewMockProviderClient(ctrl)

	client.EXPECT().GetResourceIdentitySchemas(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.GetResourceIdentitySchemas_Response{}, status.Error(codes.Unimplemented, "test error"))

	p := &GRPCProvider{
		client: client,
	}

	resp := p.GetResourceIdentitySchemas()

	checkDiags(t, resp.Diagnostics)
}

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(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(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(providers.ValidateDataResourceConfigRequest{
		TypeName: "data",
		Config:   cfg,
	})
	checkDiags(t, resp.Diagnostics)
}

func TestGRPCProvider_ValidateListResourceConfig(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	client.EXPECT().ValidateListResourceConfig(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.ValidateListResourceConfig_Response{}, nil)

	cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"config": map[string]interface{}{"filter_attr": "value"}})
	resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
		TypeName: "list",
		Config:   cfg,
	})
	checkDiags(t, resp.Diagnostics)
}

func TestGRPCProvider_ValidateListResourceConfig_OptionalCfg(t *testing.T) {
	ctrl := gomock.NewController(t)
	client := mockproto.NewMockProviderClient(ctrl)
	sch := providerProtoSchema()
	sch.ListResourceSchemas["list"].Block.Attributes[0].Optional = true
	sch.ListResourceSchemas["list"].Block.Attributes[0].Required = false
	// we always need a GetSchema method
	client.EXPECT().GetProviderSchema(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(sch, nil)

	// GetResourceIdentitySchemas is called as part of GetSchema
	client.EXPECT().GetResourceIdentitySchemas(
		gomock.Any(),
		gomock.Any(),
		gomock.Any(),
	).Return(providerResourceIdentitySchemas(), nil)

	p := &GRPCProvider{
		client: client,
	}
	client.EXPECT().ValidateListResourceConfig(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.ValidateListResourceConfig_Response{}, nil)

	converted := convert.ProtoToListSchema(sch.ListResourceSchemas["list"])
	cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{})
	coercedCfg, err := converted.Body.CoerceValue(cfg)
	if err != nil {
		t.Fatalf("failed to coerce config: %v", err)
	}
	resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
		TypeName: "list",
		Config:   coercedCfg,
	})
	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_UpgradeResourceIdentity(t *testing.T) {
	testCases := []struct {
		desc          string
		response      *proto.UpgradeResourceIdentity_Response
		expectError   bool
		expectedValue cty.Value
	}{
		{
			"successful upgrade",
			&proto.UpgradeResourceIdentity_Response{
				UpgradedIdentity: &proto.ResourceIdentityData{
					IdentityData: &proto.DynamicValue{
						Json: []byte(`{"id_attr":"bar"}`),
					},
				},
			},
			false,
			cty.ObjectVal(map[string]cty.Value{"id_attr": cty.StringVal("bar")}),
		},
		{
			"response with error diagnostic",
			&proto.UpgradeResourceIdentity_Response{
				Diagnostics: []*proto.Diagnostic{
					{
						Severity: proto.Diagnostic_ERROR,
						Summary:  "test error",
						Detail:   "test error detail",
					},
				},
			},
			true,
			cty.NilVal,
		},
		{
			"schema mismatch",
			&proto.UpgradeResourceIdentity_Response{
				UpgradedIdentity: &proto.ResourceIdentityData{
					IdentityData: &proto.DynamicValue{
						Json: []byte(`{"attr_new":"bar"}`),
					},
				},
			},
			true,
			cty.NilVal,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.desc, func(t *testing.T) {
			client := mockProviderClient(t)
			p := &GRPCProvider{
				client: client,
			}

			client.EXPECT().UpgradeResourceIdentity(
				gomock.Any(),
				gomock.Any(),
			).Return(tc.response, nil)

			resp := p.UpgradeResourceIdentity(providers.UpgradeResourceIdentityRequest{
				TypeName:        "resource",
				Version:         0,
				RawIdentityJSON: []byte(`{"old_attr":"bar"}`),
			})

			if tc.expectError {
				checkDiagsHasError(t, resp.Diagnostics)
			} else {
				checkDiags(t, resp.Diagnostics)

				if !cmp.Equal(tc.expectedValue, resp.UpgradedIdentity, typeComparer, valueComparer, equateEmpty) {
					t.Fatal(cmp.Diff(tc.expectedValue, resp.UpgradedIdentity, typeComparer, valueComparer, equateEmpty))
				}
			}
		})
	}
}

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(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()
	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_ReadResource_deferred(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"),
		},
		Deferred: &proto.Deferred{
			Reason: proto.Deferred_ABSENT_PREREQ,
		},
	}, nil)

	resp := p.ReadResource(providers.ReadResourceRequest{
		TypeName: "resource",
		PriorState: cty.ObjectVal(map[string]cty.Value{
			"attr": cty.StringVal("foo"),
		}),
	})

	checkDiags(t, resp.Diagnostics)

	expectedDeferred := &providers.Deferred{
		Reason: providers.DeferredReasonAbsentPrereq,
	}
	if !cmp.Equal(expectedDeferred, resp.Deferred, typeComparer, valueComparer, equateEmpty) {
		t.Fatal(cmp.Diff(expectedDeferred, resp.Deferred, 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_ImportResourceState_Identity(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	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"),
				},
				Identity: &proto.ResourceIdentityData{
					IdentityData: &proto.DynamicValue{
						Msgpack: []byte("\x81\xa7id_attr\xa3foo"),
					},
				},
			},
		},
	}, nil)

	resp := p.ImportResourceState(providers.ImportResourceStateRequest{
		TypeName: "resource",
		Identity: cty.ObjectVal(map[string]cty.Value{
			"id_attr": cty.StringVal("foo"),
		}),
	})

	checkDiags(t, resp.Diagnostics)

	expectedResource := providers.ImportedResource{
		TypeName: "resource",
		State: cty.ObjectVal(map[string]cty.Value{
			"attr": cty.StringVal("bar"),
		}),
		Identity: cty.ObjectVal(map[string]cty.Value{
			"id_attr": cty.StringVal("foo"),
		}),
	}

	imported := resp.ImportedResources[0]
	if !cmp.Equal(expectedResource, imported, typeComparer, valueComparer, equateEmpty) {
		t.Fatal(cmp.Diff(expectedResource, imported, typeComparer, valueComparer, equateEmpty))
	}
}

func TestGRPCProvider_MoveResourceState(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	expectedTargetPrivate := []byte(`{"target": "private"}`)
	expectedTargetState := cty.ObjectVal(map[string]cty.Value{
		"attr": cty.StringVal("bar"),
	})

	client.EXPECT().MoveResourceState(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.MoveResourceState_Response{
		TargetState: &proto.DynamicValue{
			Msgpack: []byte("\x81\xa4attr\xa3bar"),
		},
		TargetPrivate: expectedTargetPrivate,
	}, nil)

	resp := p.MoveResourceState(providers.MoveResourceStateRequest{
		SourcePrivate:   []byte(`{"source": "private"}`),
		SourceStateJSON: []byte(`{"source_attr":"bar"}`),
		TargetTypeName:  "resource",
	})

	checkDiags(t, resp.Diagnostics)

	if !cmp.Equal(expectedTargetPrivate, resp.TargetPrivate, typeComparer, valueComparer, equateEmpty) {
		t.Fatal(cmp.Diff(expectedTargetPrivate, resp.TargetPrivate, typeComparer, valueComparer, equateEmpty))
	}

	if !cmp.Equal(expectedTargetState, resp.TargetState, typeComparer, valueComparer, equateEmpty) {
		t.Fatal(cmp.Diff(expectedTargetState, resp.TargetState, typeComparer, valueComparer, equateEmpty))
	}
}

func TestGRPCProvider_MoveResourceStateJSON(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	expectedTargetPrivate := []byte(`{"target": "private"}`)
	expectedTargetState := cty.ObjectVal(map[string]cty.Value{
		"attr": cty.StringVal("bar"),
	})

	client.EXPECT().MoveResourceState(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.MoveResourceState_Response{
		TargetState: &proto.DynamicValue{
			Json: []byte(`{"attr":"bar"}`),
		},
		TargetPrivate: expectedTargetPrivate,
	}, nil)

	resp := p.MoveResourceState(providers.MoveResourceStateRequest{
		SourcePrivate:   []byte(`{"source": "private"}`),
		SourceStateJSON: []byte(`{"source_attr":"bar"}`),
		TargetTypeName:  "resource",
	})

	checkDiags(t, resp.Diagnostics)

	if !cmp.Equal(expectedTargetPrivate, resp.TargetPrivate, typeComparer, valueComparer, equateEmpty) {
		t.Fatal(cmp.Diff(expectedTargetPrivate, resp.TargetPrivate, typeComparer, valueComparer, equateEmpty))
	}

	if !cmp.Equal(expectedTargetState, resp.TargetState, typeComparer, valueComparer, equateEmpty) {
		t.Fatal(cmp.Diff(expectedTargetState, resp.TargetState, 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_openEphemeralResource(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	client.EXPECT().OpenEphemeralResource(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.OpenEphemeralResource_Response{
		Result: &proto.DynamicValue{
			Msgpack: []byte("\x81\xa4attr\xa3bar"),
		},
		RenewAt: timestamppb.New(time.Now().Add(time.Second)),
		Private: []byte("private data"),
	}, nil)

	resp := p.OpenEphemeralResource(providers.OpenEphemeralResourceRequest{
		TypeName: "ephemeral",
		Config: cty.ObjectVal(map[string]cty.Value{
			"attr": cty.NullVal(cty.String),
		}),
	})

	checkDiags(t, resp.Diagnostics)

	expected := cty.ObjectVal(map[string]cty.Value{
		"attr": cty.StringVal("bar"),
	})

	if !cmp.Equal(expected, resp.Result, typeComparer, valueComparer, equateEmpty) {
		t.Fatal(cmp.Diff(expected, resp.Result, typeComparer, valueComparer, equateEmpty))
	}

	if !resp.RenewAt.After(time.Now()) {
		t.Fatal("invalid RenewAt:", resp.RenewAt)
	}

	if !bytes.Equal(resp.Private, []byte("private data")) {
		t.Fatalf("invalid private data: %q", resp.Private)
	}
}

func TestGRPCProvider_renewEphemeralResource(t *testing.T) {
	client := mockproto.NewMockProviderClient(gomock.NewController(t))
	p := &GRPCProvider{
		client: client,
	}

	client.EXPECT().RenewEphemeralResource(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.RenewEphemeralResource_Response{
		RenewAt: timestamppb.New(time.Now().Add(time.Second)),
		Private: []byte("private data"),
	}, nil)

	resp := p.RenewEphemeralResource(providers.RenewEphemeralResourceRequest{
		TypeName: "ephemeral",
		Private:  []byte("private data"),
	})

	checkDiags(t, resp.Diagnostics)

	if !resp.RenewAt.After(time.Now()) {
		t.Fatal("invalid RenewAt:", resp.RenewAt)
	}

	if !bytes.Equal(resp.Private, []byte("private data")) {
		t.Fatalf("invalid private data: %q", resp.Private)
	}
}

func TestGRPCProvider_closeEphemeralResource(t *testing.T) {
	client := mockproto.NewMockProviderClient(gomock.NewController(t))
	p := &GRPCProvider{
		client: client,
	}

	client.EXPECT().CloseEphemeralResource(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.CloseEphemeralResource_Response{}, nil)

	resp := p.CloseEphemeralResource(providers.CloseEphemeralResourceRequest{
		TypeName: "ephemeral",
		Private:  []byte("private data"),
	})

	checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_GetSchema_ListResourceTypes(t *testing.T) {
	p := &GRPCProvider{
		client: mockProviderClient(t),
		ctx:    context.Background(),
	}

	resp := p.GetProviderSchema()
	listResourceSchema := resp.ListResourceTypes
	expected := map[string]providers.Schema{
		"list": {
			Version: 1,
			Body: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"data": {
						Type:     cty.DynamicPseudoType,
						Computed: true,
					},
				},
				BlockTypes: map[string]*configschema.NestedBlock{
					"config": {
						Block: configschema.Block{
							Attributes: map[string]*configschema.Attribute{
								"filter_attr": {
									Type:     cty.String,
									Required: true,
								},
							},
						},
						Nesting:  configschema.NestingSingle,
						MinItems: 1,
						MaxItems: 1,
					},
				},
			},
		},
	}
	checkDiags(t, resp.Diagnostics)

	actualBody := convert.ConfigSchemaToProto(listResourceSchema["list"].Body).String()
	expectedBody := convert.ConfigSchemaToProto(expected["list"].Body).String()
	if diff := cmp.Diff(expectedBody, actualBody); diff != "" {
		t.Fatalf("unexpected body (-want +got):\n%s", diff)
	}
}

func TestGRPCProvider_Encode(t *testing.T) {
	// TODO: This is the only test in this package that imports plans. If that
	// ever leads to a circular import, we should consider moving this test to
	// a different package or refactoring the test to not use plans.
	p := &GRPCProvider{
		client: mockProviderClient(t),
		ctx:    context.Background(),
		Addr:   addrs.ImpliedProviderForUnqualifiedType("testencode"),
	}
	resp := p.GetProviderSchema()

	src := plans.NewChanges()
	src.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChange{
		Addr: addrs.AbsResourceInstance{
			Module: addrs.RootModuleInstance,
			Resource: addrs.ResourceInstance{
				Resource: addrs.Resource{
					Mode: addrs.ListResourceMode,
					Type: "list",
					Name: "test",
				},
				Key: addrs.NoKey,
			},
		},
		ProviderAddr: addrs.AbsProviderConfig{
			Provider: p.Addr,
		},
		Change: plans.Change{
			Before: cty.NullVal(cty.Object(map[string]cty.Type{
				"config": cty.Object(map[string]cty.Type{
					"filter_attr": cty.String,
				}),
				"data": cty.List(cty.Object(map[string]cty.Type{
					"state": cty.Object(map[string]cty.Type{
						"resource_attr": cty.String,
					}),
					"identity": cty.Object(map[string]cty.Type{
						"id_attr": cty.String,
					}),
				})),
			})),
			After: cty.ObjectVal(map[string]cty.Value{
				"config": cty.ObjectVal(map[string]cty.Value{
					"filter_attr": cty.StringVal("value"),
				}),
				"data": cty.ListVal([]cty.Value{
					cty.ObjectVal(map[string]cty.Value{
						"state": cty.ObjectVal(map[string]cty.Value{
							"resource_attr": cty.StringVal("value"),
						}),
						"identity": cty.ObjectVal(map[string]cty.Value{
							"id_attr": cty.StringVal("value"),
						}),
					}),
				}),
			}),
		},
	})
	_, err := src.Encode(&schemarepo.Schemas{
		Providers: map[addrs.Provider]providers.ProviderSchema{
			p.Addr: {
				ResourceTypes:     resp.ResourceTypes,
				ListResourceTypes: resp.ListResourceTypes,
			},
		},
	})
	if err != nil {
		t.Fatalf("unexpected error encoding changes: %s", err)
	}
}

// Mock implementation of the ListResource stream client
type mockListResourceStreamClient struct {
	events  []*proto.ListResource_Event
	current int
	proto.Provider_ListResourceClient
}

func (m *mockListResourceStreamClient) Recv() (*proto.ListResource_Event, error) {
	if m.current >= len(m.events) {
		return nil, io.EOF
	}

	event := m.events[m.current]
	m.current++
	return event, nil
}

func TestGRPCProvider_ListResource(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
		ctx:    context.Background(),
	}

	// Create a mock stream client that will return resource events
	mockStream := &mockListResourceStreamClient{
		events: []*proto.ListResource_Event{
			{
				DisplayName: "Test Resource 1",
				Identity: &proto.ResourceIdentityData{
					IdentityData: &proto.DynamicValue{
						Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
					},
				},
			},
			{
				DisplayName: "Test Resource 2",
				Identity: &proto.ResourceIdentityData{
					IdentityData: &proto.DynamicValue{
						Msgpack: []byte("\x81\xa7id_attr\xa4id-2"),
					},
				},
				ResourceObject: &proto.DynamicValue{
					Msgpack: []byte("\x81\xadresource_attr\xa5value"),
				},
			},
		},
	}

	client.EXPECT().ListResource(
		gomock.Any(),
		gomock.Any(),
	).Return(mockStream, nil)

	// Create the request
	configVal := cty.ObjectVal(map[string]cty.Value{
		"config": cty.ObjectVal(map[string]cty.Value{
			"filter_attr": cty.StringVal("filter-value"),
		}),
	})
	request := providers.ListResourceRequest{
		TypeName:              "list",
		Config:                configVal,
		IncludeResourceObject: true,
		Limit:                 100,
	}

	resp := p.ListResource(request)
	checkDiags(t, resp.Diagnostics)

	data := resp.Result.AsValueMap()
	if _, ok := data["data"]; !ok {
		t.Fatal("Expected 'data' key in result")
	}
	// Verify that we received both events
	if len(data["data"].AsValueSlice()) != 2 {
		t.Fatalf("Expected 2 resources, got %d", len(data["data"].AsValueSlice()))
	}
	results := data["data"].AsValueSlice()

	// Verify first event
	displayName := results[0].GetAttr("display_name")
	if displayName.AsString() != "Test Resource 1" {
		t.Errorf("Expected DisplayName 'Test Resource 1', got '%s'", displayName.AsString())
	}

	expectedId1 := cty.ObjectVal(map[string]cty.Value{
		"id_attr": cty.StringVal("id-1"),
	})

	identity := results[0].GetAttr("identity")
	if !identity.RawEquals(expectedId1) {
		t.Errorf("Expected Identity %#v, got %#v", expectedId1, identity)
	}

	// ResourceObject should be null for the first event as it wasn't provided
	resourceObject := results[0].GetAttr("state")
	if !resourceObject.IsNull() {
		t.Errorf("Expected ResourceObject to be null, got %#v", resourceObject)
	}

	// Verify second event
	displayName = results[1].GetAttr("display_name")
	if displayName.AsString() != "Test Resource 2" {
		t.Errorf("Expected DisplayName 'Test Resource 2', got '%s'", displayName.AsString())
	}

	expectedId2 := cty.ObjectVal(map[string]cty.Value{
		"id_attr": cty.StringVal("id-2"),
	})
	identity = results[1].GetAttr("identity")
	if !identity.RawEquals(expectedId2) {
		t.Errorf("Expected Identity %#v, got %#v", expectedId2, identity)
	}

	expectedResource := cty.ObjectVal(map[string]cty.Value{
		"resource_attr": cty.StringVal("value"),
	})
	resourceObject = results[1].GetAttr("state")
	if !resourceObject.RawEquals(expectedResource) {
		t.Errorf("Expected ResourceObject %#v, got %#v", expectedResource, resourceObject)
	}
}

func TestGRPCProvider_ListResource_Error(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
		ctx:    context.Background(),
	}

	// Test case where the provider returns an error
	client.EXPECT().ListResource(
		gomock.Any(),
		gomock.Any(),
	).Return(nil, fmt.Errorf("provider error"))

	configVal := cty.ObjectVal(map[string]cty.Value{
		"config": cty.ObjectVal(map[string]cty.Value{
			"filter_attr": cty.StringVal("filter-value"),
		}),
	})
	request := providers.ListResourceRequest{
		TypeName: "list",
		Config:   configVal,
	}

	resp := p.ListResource(request)
	checkDiagsHasError(t, resp.Diagnostics)
}

func TestGRPCProvider_ListResource_Diagnostics(t *testing.T) {
	configVal := cty.ObjectVal(map[string]cty.Value{
		"config": cty.ObjectVal(map[string]cty.Value{
			"filter_attr": cty.StringVal("filter-value"),
		}),
	})
	request := providers.ListResourceRequest{
		TypeName: "list",
		Config:   configVal,
		Limit:    100,
	}

	testCases := []struct {
		name          string
		events        []*proto.ListResource_Event
		expectedCount int
		expectedDiags int
		expectedWarns int // subset of expectedDiags
	}{
		{
			"no events",
			[]*proto.ListResource_Event{},
			0,
			0,
			0,
		},
		{
			"single event no diagnostics",
			[]*proto.ListResource_Event{
				{
					DisplayName: "Test Resource",
					Identity: &proto.ResourceIdentityData{
						IdentityData: &proto.DynamicValue{
							Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
						},
					},
				},
			},
			1,
			0,
			0,
		},
		{
			"event with warning",
			[]*proto.ListResource_Event{
				{
					DisplayName: "Test Resource",
					Identity: &proto.ResourceIdentityData{
						IdentityData: &proto.DynamicValue{
							Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
						},
					},
					Diagnostic: []*proto.Diagnostic{
						{
							Severity: proto.Diagnostic_WARNING,
							Summary:  "Test warning",
							Detail:   "Warning detail",
						},
					},
				},
			},
			1,
			1,
			1,
		},
		{
			"only a warning",
			[]*proto.ListResource_Event{
				{
					Diagnostic: []*proto.Diagnostic{
						{
							Severity: proto.Diagnostic_WARNING,
							Summary:  "Test warning",
							Detail:   "Warning detail",
						},
					},
				},
			},
			0,
			1,
			1,
		},
		{
			"only an error",
			[]*proto.ListResource_Event{
				{
					Diagnostic: []*proto.Diagnostic{
						{
							Severity: proto.Diagnostic_ERROR,
							Summary:  "Test error",
							Detail:   "Error detail",
						},
					},
				},
			},
			0,
			1,
			0,
		},
		{
			"event with error",
			[]*proto.ListResource_Event{
				{
					DisplayName: "Test Resource",
					Identity: &proto.ResourceIdentityData{
						IdentityData: &proto.DynamicValue{
							Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
						},
					},
					Diagnostic: []*proto.Diagnostic{
						{
							Severity: proto.Diagnostic_ERROR,
							Summary:  "Test error",
							Detail:   "Error detail",
						},
					},
				},
			},
			0,
			1,
			0,
		},
		{
			"multiple events mixed diagnostics",
			[]*proto.ListResource_Event{
				{
					DisplayName: "Resource 1",
					Identity: &proto.ResourceIdentityData{
						IdentityData: &proto.DynamicValue{
							Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
						},
					},
					Diagnostic: []*proto.Diagnostic{
						{
							Severity: proto.Diagnostic_WARNING,
							Summary:  "Warning 1",
							Detail:   "Warning detail 1",
						},
					},
				},
				{
					DisplayName: "Resource 2",
					Identity: &proto.ResourceIdentityData{
						IdentityData: &proto.DynamicValue{
							Msgpack: []byte("\x81\xa7id_attr\xa4id-2"),
						},
					},
				},
				{
					DisplayName: "Resource 3",
					Identity: &proto.ResourceIdentityData{
						IdentityData: &proto.DynamicValue{
							Msgpack: []byte("\x81\xa7id_attr\xa4id-3"),
						},
					},
					Diagnostic: []*proto.Diagnostic{
						{
							Severity: proto.Diagnostic_ERROR,
							Summary:  "Error 1",
							Detail:   "Error detail 1",
						},
						{
							Severity: proto.Diagnostic_WARNING,
							Summary:  "Warning 2",
							Detail:   "Warning detail 2",
						},
					},
				},
				{ // This event will never be reached
					DisplayName: "Resource 4",
					Identity: &proto.ResourceIdentityData{
						IdentityData: &proto.DynamicValue{
							Msgpack: []byte("\x81\xa7id_attr\xa4id-4"),
						},
					},
				},
			},
			2,
			3,
			2,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			client := mockProviderClient(t)
			p := &GRPCProvider{
				client: client,
				ctx:    context.Background(),
			}

			mockStream := &mockListResourceStreamClient{
				events: tc.events,
			}

			client.EXPECT().ListResource(
				gomock.Any(),
				gomock.Any(),
			).Return(mockStream, nil)

			resp := p.ListResource(request)

			result := resp.Result.AsValueMap()
			nResults := result["data"].LengthInt()
			if nResults != tc.expectedCount {
				t.Fatalf("Expected %d results, got %d", tc.expectedCount, nResults)
			}

			nDiagnostics := len(resp.Diagnostics)
			if nDiagnostics != tc.expectedDiags {
				t.Fatalf("Expected %d diagnostics, got %d", tc.expectedDiags, nDiagnostics)
			}

			nWarnings := len(resp.Diagnostics.Warnings())
			if nWarnings != tc.expectedWarns {
				t.Fatalf("Expected %d warnings, got %d", tc.expectedWarns, nWarnings)
			}
		})
	}
}

func TestGRPCProvider_ListResource_Limit(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
		ctx:    context.Background(),
	}

	// Create a mock stream client that will return resource events
	mockStream := &mockListResourceStreamClient{
		events: []*proto.ListResource_Event{
			{
				DisplayName: "Test Resource 1",
				Identity: &proto.ResourceIdentityData{
					IdentityData: &proto.DynamicValue{
						Msgpack: []byte("\x81\xa7id_attr\xa4id-1"),
					},
				},
			},
			{
				DisplayName: "Test Resource 2",
				Identity: &proto.ResourceIdentityData{
					IdentityData: &proto.DynamicValue{
						Msgpack: []byte("\x81\xa7id_attr\xa4id-2"),
					},
				},
			},
			{
				DisplayName: "Test Resource 3",
				Identity: &proto.ResourceIdentityData{
					IdentityData: &proto.DynamicValue{
						Msgpack: []byte("\x81\xa7id_attr\xa4id-3"),
					},
				},
			},
		},
	}

	client.EXPECT().ListResource(
		gomock.Any(),
		gomock.Any(),
	).Return(mockStream, nil)

	// Create the request
	configVal := cty.ObjectVal(map[string]cty.Value{
		"config": cty.ObjectVal(map[string]cty.Value{
			"filter_attr": cty.StringVal("filter-value"),
		}),
	})
	request := providers.ListResourceRequest{
		TypeName: "list",
		Config:   configVal,
		Limit:    2,
	}

	resp := p.ListResource(request)
	checkDiags(t, resp.Diagnostics)

	data := resp.Result.AsValueMap()
	if _, ok := data["data"]; !ok {
		t.Fatal("Expected 'data' key in result")
	}
	// Verify that we received both events
	if len(data["data"].AsValueSlice()) != 2 {
		t.Fatalf("Expected 2 resources, got %d", len(data["data"].AsValueSlice()))
	}
	results := data["data"].AsValueSlice()

	// Verify that we received both events
	if len(results) != 2 {
		t.Fatalf("Expected 2 events, got %d", len(results))
	}
}

func TestGRPCProvider_planAction_valid(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
		ctx:    context.Background(),
	}

	client.EXPECT().PlanAction(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.PlanAction_Response{}, nil)

	resp := p.PlanAction(providers.PlanActionRequest{
		ActionType: "action",
		ProposedActionData: cty.ObjectVal(map[string]cty.Value{
			"attr": cty.StringVal("foo"),
		}),
	})

	checkDiags(t, resp.Diagnostics)
}

func TestGRPCProvider_planAction_valid_but_fails(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	client.EXPECT().PlanAction(
		gomock.Any(),
		gomock.Any(),
	).Return(&proto.PlanAction_Response{
		Diagnostics: []*proto.Diagnostic{
			{
				Severity: proto.Diagnostic_ERROR,
				Summary:  "Boom",
				Detail:   "Explosion",
			},
		},
	}, nil)

	resp := p.PlanAction(providers.PlanActionRequest{
		ActionType: "action",
		ProposedActionData: cty.ObjectVal(map[string]cty.Value{
			"attr": cty.StringVal("foo"),
		}),
	})

	checkDiagsHasError(t, resp.Diagnostics)
}

func TestGRPCProvider_planAction_invalid_config(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	resp := p.PlanAction(providers.PlanActionRequest{
		ActionType: "action",
		ProposedActionData: cty.ObjectVal(map[string]cty.Value{
			"not_the_right_attr": cty.StringVal("foo"),
		}),
	})

	checkDiagsHasError(t, resp.Diagnostics)
}

func TestGRPCProvider_invokeAction_valid(t *testing.T) {
	ctrl := gomock.NewController(t)
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	mockInvokeClient := mockproto.NewMockProvider_InvokeActionClient[tfplugin6.InvokeAction_Event](ctrl)
	mockInvokeClient.EXPECT().Recv().Return(&proto.InvokeAction_Event{
		Type: &proto.InvokeAction_Event_Progress_{
			Progress: &proto.InvokeAction_Event_Progress{
				Message: "Hello from the action",
			},
		},
	}, nil)
	mockInvokeClient.EXPECT().Recv().Return(&proto.InvokeAction_Event{
		Type: &proto.InvokeAction_Event_Completed_{
			Completed: &proto.InvokeAction_Event_Completed{},
		},
	}, nil)
	mockInvokeClient.EXPECT().Recv().Return(nil, io.EOF)

	client.EXPECT().InvokeAction(
		gomock.Any(),
		gomock.Any(),
	).Return(mockInvokeClient, nil)

	resp := p.InvokeAction(providers.InvokeActionRequest{
		ActionType: "action",
		PlannedActionData: cty.ObjectVal(map[string]cty.Value{
			"attr": cty.StringVal("foo"),
		}),
	})

	evts := []providers.InvokeActionEvent{}
	for e := range resp.Events {
		evts = append(evts, e)
	}

	if len(evts) != 2 {
		t.Fatalf("expected 2 events, got %d", len(evts))
	}

	checkDiags(t, resp.Diagnostics)
}

func TestGRPCProvider_invokeAction_invalid(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}

	resp := p.InvokeAction(providers.InvokeActionRequest{
		ActionType: "action",
		PlannedActionData: cty.ObjectVal(map[string]cty.Value{
			"not-defined": cty.StringVal("foo"),
		}),
	})

	checkDiagsHasError(t, resp.Diagnostics)
}

func TestGRPCProvider_ValidateStateStoreConfig_returns_validation_errors(t *testing.T) {
	storeName := "mock_store" // mockProviderClient returns a mock that has this state store in its schemas

	t.Run("no validation error raised", func(t *testing.T) {
		typeName := storeName
		var diagnostic []*proto.Diagnostic = nil

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		client.EXPECT().ValidateStateStoreConfig(
			gomock.Any(),
			gomock.Any(),
		).Return(&proto.ValidateStateStore_Response{
			Diagnostics: diagnostic,
		}, nil)

		request := providers.ValidateStateStoreConfigRequest{
			TypeName: typeName,
			Config: cty.ObjectVal(map[string]cty.Value{
				"region": cty.StringVal("neptune"),
			}),
		}

		// Act
		resp := p.ValidateStateStoreConfig(request)

		// Assert no error returned
		checkDiags(t, resp.Diagnostics)

	})

	t.Run("validation error raised", func(t *testing.T) {
		typeName := storeName
		diagnostic := []*proto.Diagnostic{
			{
				Severity: proto.Diagnostic_ERROR,
				Summary:  "Error from ValidateStateStoreConfig",
				Detail:   "Something went wrong",
			},
		}
		errorText := "Error from ValidateStateStoreConfig"

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		client.EXPECT().ValidateStateStoreConfig(
			gomock.Any(),
			gomock.Any(),
		).Return(&proto.ValidateStateStore_Response{
			Diagnostics: diagnostic,
		}, nil)

		request := providers.ValidateStateStoreConfigRequest{
			TypeName: typeName,
			Config: cty.ObjectVal(map[string]cty.Value{
				"region": cty.StringVal("neptune"),
			}),
		}

		// Act
		resp := p.ValidateStateStoreConfig(request)

		// Assert error returned
		checkDiagsHasError(t, resp.Diagnostics)
		if resp.Diagnostics[0].Description().Summary != errorText {
			t.Fatalf("expected error summary to be %q, but got %q",
				errorText,
				resp.Diagnostics[0].Description().Summary,
			)
		}

	})

}

func TestGRPCProvider_ValidateStateStoreConfig_schema_errors(t *testing.T) {
	t.Run("no matching store type in provider", func(t *testing.T) {
		typeName := "does_not_exist" // not present in mockProviderClient state store schemas
		config := cty.EmptyObjectVal
		expectedErrorSummary := "unknown state store type \"does_not_exist\""

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		request := providers.ValidateStateStoreConfigRequest{
			TypeName: typeName,
			Config:   config,
		}

		// Act
		resp := p.ValidateStateStoreConfig(request)

		// Note - we haven't asserted that we expect ValidateStateStoreConfig
		// to be called via the client; this package returns these errors before then.

		// Assert that the expected error is returned
		checkDiagsHasError(t, resp.Diagnostics)
		if resp.Diagnostics[0].Description().Summary != expectedErrorSummary {
			t.Fatalf("expected error summary to be %q, but got %q",
				expectedErrorSummary,
				resp.Diagnostics[0].Description().Summary,
			)
		}
	})

	t.Run("missing required attributes", func(t *testing.T) {
		typeName := "mock_store" // Is present in mockProviderClient
		config := cty.ObjectVal(map[string]cty.Value{
			// Missing required `region` attr
		})
		expectedErrorSummary := "attribute \"region\" is required"

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		request := providers.ValidateStateStoreConfigRequest{
			TypeName: typeName,
			Config:   config,
		}

		// Act
		resp := p.ValidateStateStoreConfig(request)

		// Note - we haven't asserted that we expect ValidateStateStoreConfig
		// to be called via the client; this package returns these errors before then.

		// Assert that the expected error is returned
		checkDiagsHasError(t, resp.Diagnostics)
		if resp.Diagnostics[0].Description().Summary != expectedErrorSummary {
			t.Fatalf("expected error summary to be %q, but got %q",
				expectedErrorSummary,
				resp.Diagnostics[0].Description().Summary,
			)
		}
	})
}

func TestGRPCProvider_ConfigureStateStore_returns_validation_errors(t *testing.T) {
	storeName := "mock_store" // mockProviderClient returns a mock that has this state store in its schemas
	chunkSize := 4 << 20      // 4MB

	t.Run("no validation error raised", func(t *testing.T) {
		typeName := storeName
		var diagnostic []*proto.Diagnostic = nil

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		client.EXPECT().ConfigureStateStore(
			gomock.Any(),
			gomock.Any(),
		).Return(&proto.ConfigureStateStore_Response{
			Capabilities: &proto.StateStoreServerCapabilities{
				ChunkSize: int64(chunkSize),
			},
			Diagnostics: diagnostic,
		}, nil)

		request := providers.ConfigureStateStoreRequest{
			TypeName: typeName,
			Config: cty.ObjectVal(map[string]cty.Value{
				"region": cty.StringVal("neptune"),
			}),
			Capabilities: providers.StateStoreClientCapabilities{
				ChunkSize: int64(chunkSize),
			},
		}

		// Act
		resp := p.ConfigureStateStore(request)

		// Assert no error returned
		checkDiags(t, resp.Diagnostics)
	})

	t.Run("validation error raised", func(t *testing.T) {
		typeName := storeName
		diagnostic := []*proto.Diagnostic{
			{
				Severity: proto.Diagnostic_ERROR,
				Summary:  "Error from ConfigureStateStore",
				Detail:   "Something went wrong",
			},
		}
		errorText := "Error from ConfigureStateStore"

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		client.EXPECT().ConfigureStateStore(
			gomock.Any(),
			gomock.Any(),
		).Return(&proto.ConfigureStateStore_Response{
			Capabilities: &proto.StateStoreServerCapabilities{
				ChunkSize: int64(chunkSize),
			},
			Diagnostics: diagnostic,
		}, nil)

		request := providers.ConfigureStateStoreRequest{
			TypeName: typeName,
			Config: cty.ObjectVal(map[string]cty.Value{
				"region": cty.StringVal("neptune"),
			}),
			Capabilities: providers.StateStoreClientCapabilities{
				ChunkSize: int64(chunkSize),
			},
		}

		// Act
		resp := p.ConfigureStateStore(request)

		// Assert whether error returned or not
		checkDiagsHasError(t, resp.Diagnostics)
		if resp.Diagnostics[0].Description().Summary != errorText {
			t.Fatalf("expected error summary to be %q, but got %q",
				errorText,
				resp.Diagnostics[0].Description().Summary,
			)
		}

	})

}

func TestGRPCProvider_ConfigureStateStore_schema_errors(t *testing.T) {
	t.Run("no matching store type in provider", func(t *testing.T) {
		typeName := "does_not_exist" // not present in mockProviderClient state store schemas
		config := cty.EmptyObjectVal
		expectedErrorSummary := "unknown state store type \"does_not_exist\""

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		request := providers.ConfigureStateStoreRequest{
			TypeName: typeName,
			Config:   config,
		}

		// Act
		resp := p.ConfigureStateStore(request)

		// Note - we haven't asserted that we expect ConfigureStateStore
		// to be called via the client; this package returns these errors before then.

		// Assert that the expected error is returned
		checkDiagsHasError(t, resp.Diagnostics)
		if resp.Diagnostics[0].Description().Summary != expectedErrorSummary {
			t.Fatalf("expected error summary to be %q, but got %q",
				expectedErrorSummary,
				resp.Diagnostics[0].Description().Summary,
			)
		}
	})

	t.Run("missing required attributes", func(t *testing.T) {
		typeName := "mock_store" // Is present in mockProviderClient
		config := cty.ObjectVal(map[string]cty.Value{
			// Missing required `region` attr
		})
		expectedErrorSummary := "attribute \"region\" is required"

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		request := providers.ConfigureStateStoreRequest{
			TypeName: typeName,
			Config:   config,
		}

		// Act
		resp := p.ConfigureStateStore(request)

		// Note - we haven't asserted that we expect ConfigureStateStore
		// to be called via the client; this package returns these errors before then.

		// Assert that the expected error is returned
		checkDiagsHasError(t, resp.Diagnostics)
		if resp.Diagnostics[0].Description().Summary != expectedErrorSummary {
			t.Fatalf("expected error summary to be %q, but got %q",
				expectedErrorSummary,
				resp.Diagnostics[0].Description().Summary,
			)
		}
	})
}

func TestGRPCProvider_GetStates(t *testing.T) {
	t.Run("returns expected values", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		client.EXPECT().GetStates(
			gomock.Any(),
			gomock.Any(),
		).Return(&proto.GetStates_Response{
			StateId: []string{"default"},
			Diagnostics: []*proto.Diagnostic{
				{
					Severity: proto.Diagnostic_ERROR,
					Summary:  "Error from GetStates",
					Detail:   "Something went wrong",
				},
			},
		}, nil)

		request := providers.GetStatesRequest{
			TypeName: "mock_store",
		}

		// Act
		resp := p.GetStates(request)

		// Assert returned values
		if len(resp.States) != 1 || resp.States[0] != "default" {
			t.Fatalf("expected the returned states to be [\"default\"], instead got: %s", resp.States)
		}

		checkDiagsHasError(t, resp.Diagnostics)
		expectedErrorSummary := "Error from GetStates"
		if resp.Diagnostics[0].Description().Summary != expectedErrorSummary {
			t.Fatalf("expected error summary to be %q, but got %q",
				expectedErrorSummary,
				resp.Diagnostics[0].Description().Summary,
			)
		}
	})

	t.Run("no matching store type in provider", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		request := providers.GetStatesRequest{
			TypeName: "does_not_exist", // not present in mockProviderClient state store schemas
		}

		// Act
		resp := p.GetStates(request)

		checkDiagsHasError(t, resp.Diagnostics)
		expectedErrorSummary := "unknown state store type \"does_not_exist\""
		if resp.Diagnostics[0].Description().Summary != expectedErrorSummary {
			t.Fatalf("expected error summary to be %q, but got %q",
				expectedErrorSummary,
				resp.Diagnostics[0].Description().Summary,
			)
		}
	})
}

func TestGRPCProvider_DeleteState(t *testing.T) {
	t.Run("returns expected values", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		client.EXPECT().DeleteState(
			gomock.Any(),
			gomock.Any(),
		).Return(&proto.DeleteState_Response{
			Diagnostics: []*proto.Diagnostic{
				{
					Severity: proto.Diagnostic_ERROR,
					Summary:  "Error from DeleteState",
					Detail:   "Something went wrong",
				},
			},
		}, nil)

		request := providers.DeleteStateRequest{
			TypeName: "mock_store",
		}

		// Act
		resp := p.DeleteState(request)

		// Assert returned values
		checkDiagsHasError(t, resp.Diagnostics)
		expectedErrorSummary := "Error from DeleteState"
		if resp.Diagnostics[0].Description().Summary != expectedErrorSummary {
			t.Fatalf("expected error summary to be %q, but got %q",
				expectedErrorSummary,
				resp.Diagnostics[0].Description().Summary,
			)
		}
	})

	t.Run("no matching store type in provider", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}

		request := providers.DeleteStateRequest{
			TypeName: "does_not_exist", // not present in mockProviderClient state store schemas
		}

		// Act
		resp := p.DeleteState(request)

		checkDiagsHasError(t, resp.Diagnostics)
		expectedErrorSummary := "unknown state store type \"does_not_exist\""
		if resp.Diagnostics[0].Description().Summary != expectedErrorSummary {
			t.Fatalf("expected error summary to be %q, but got %q",
				expectedErrorSummary,
				resp.Diagnostics[0].Description().Summary,
			)
		}
	})
}

func TestGRPCProvider_ReadStateBytes(t *testing.T) {
	chunkSize := 4 << 20 // 4MB

	t.Run("can process multiple chunks", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", 5)

		// Call to ReadStateBytes
		// > Assert the arguments received
		// > Define the returned mock client
		expectedReq := &proto.ReadStateBytes_Request{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
		}
		mockReadBytesClient := mockReadStateBytesClient(t)
		client.EXPECT().ReadStateBytes(
			gomock.Any(),
			gomock.Eq(expectedReq),
			gomock.Any(),
		).Return(mockReadBytesClient, nil)

		// Define what will be returned by each call to Recv
		chunks := []string{"hello", "world"}
		totalLength := len(chunks[0]) + len(chunks[1])
		mockResp := map[int]struct {
			resp *proto.ReadStateBytes_Response
			err  error
		}{
			0: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunks[0]),
					TotalLength: int64(totalLength),
					Range: &proto.StateRange{
						Start: 0,
						End:   int64(len(chunks[0])) - 1,
					},
				},
				err: nil,
			},
			1: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunks[1]),
					TotalLength: int64(totalLength),
					Range: &proto.StateRange{
						Start: int64(len(chunks[0])),
						End:   int64(len(chunks[1])) - 1,
					},
				},
				err: nil,
			},
			2: {
				resp: &proto.ReadStateBytes_Response{},
				err:  io.EOF,
			},
		}
		var count int
		mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_Response, error) {
			ret := mockResp[count]
			count++
			return ret.resp, ret.err
		}).Times(3)

		// There will be a call to CloseSend to close the stream
		mockReadBytesClient.EXPECT().CloseSend().Return(nil).Times(1)

		// Act
		request := providers.ReadStateBytesRequest{
			TypeName: expectedReq.TypeName,
			StateId:  expectedReq.StateId,
		}
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiags(t, resp.Diagnostics)
		if string(resp.Bytes) != "helloworld" {
			t.Fatalf("expected data to be %q, got: %q", "helloworld", string(resp.Bytes))
		}
	})

	t.Run("can process multiple chunks when last chunk size is one byte", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", 5)

		// Call to ReadStateBytes
		// > Assert the arguments received
		// > Define the returned mock client
		mockReadBytesClient := mockReadStateBytesClient(t)

		expectedReq := &proto.ReadStateBytes_Request{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
		}
		client.EXPECT().ReadStateBytes(
			gomock.Any(),
			gomock.Eq(expectedReq),
			gomock.Any(),
		).Return(mockReadBytesClient, nil)

		// Define what will be returned by each call to Recv
		chunk := "helloworld!"
		totalLength := len(chunk)
		mockResp := map[int]struct {
			resp *proto.ReadStateBytes_Response
			err  error
		}{
			0: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunk[:5]),
					TotalLength: int64(totalLength),
					Range: &proto.StateRange{
						Start: 0,
						End:   4,
					},
					Diagnostics: []*proto.Diagnostic{},
				},
				err: nil,
			},
			1: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunk[5:10]),
					TotalLength: int64(totalLength),
					Range: &proto.StateRange{
						Start: 5,
						End:   9,
					},
					Diagnostics: []*proto.Diagnostic{},
				},
				err: nil,
			},
			2: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunk[10:]),
					TotalLength: int64(totalLength),
					Range: &proto.StateRange{
						Start: 10,
						End:   10,
					},
					Diagnostics: []*proto.Diagnostic{},
				},
				err: nil,
			},
			3: {
				resp: &proto.ReadStateBytes_Response{},
				err:  io.EOF,
			},
		}
		var count int
		mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_Response, error) {
			ret := mockResp[count]
			count++
			return ret.resp, ret.err
		}).Times(4)

		// There will be a call to CloseSend to close the stream
		mockReadBytesClient.EXPECT().CloseSend().Return(nil).Times(1)

		// Act
		request := providers.ReadStateBytesRequest{
			TypeName: expectedReq.TypeName,
			StateId:  expectedReq.StateId,
		}
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiags(t, resp.Diagnostics)

		// Chunk size mismatches are warnings so ensure there aren't any
		if resp.Diagnostics.HasWarnings() {
			t.Fatal(resp.Diagnostics.ErrWithWarnings())
		}

		if string(resp.Bytes) != "helloworld!" {
			t.Fatalf("expected data to be %q, got: %q", "helloworld!", string(resp.Bytes))
		}
	})

	t.Run("an error diagnostic is returned when final length does not match expectations", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", 5)

		// Call to ReadStateBytes
		// > Assert the arguments received
		// > Define the returned mock client
		mockReadBytesClient := mockReadStateBytesClient(t)
		expectedReq := &proto.ReadStateBytes_Request{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
		}
		client.EXPECT().ReadStateBytes(
			gomock.Any(),
			gomock.Eq(expectedReq),
			gomock.Any(),
		).Return(mockReadBytesClient, nil)

		// Define what will be returned by each call to Recv
		chunks := []string{"hello", "world"}
		var incorrectLength int64 = 999
		correctLength := len(chunks[0]) + len(chunks[1])
		mockResp := map[int]struct {
			resp *proto.ReadStateBytes_Response
			err  error
		}{
			0: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunks[0]),
					TotalLength: incorrectLength,
					Range: &proto.StateRange{
						Start: 0,
						End:   int64(len(chunks[0])) - 1,
					},
				},
				err: nil,
			},
			1: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunks[1]),
					TotalLength: incorrectLength,
					Range: &proto.StateRange{
						Start: int64(len(chunks[0])),
						End:   int64(len(chunks[1])) - 1,
					},
				},
				err: nil,
			},
			2: {
				resp: &proto.ReadStateBytes_Response{},
				err:  io.EOF,
			},
		}
		var count int
		mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_Response, error) {
			ret := mockResp[count]
			count++
			return ret.resp, ret.err
		}).Times(3)

		// Act
		request := providers.ReadStateBytesRequest{
			TypeName: expectedReq.TypeName,
			StateId:  expectedReq.StateId,
		}
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiagsHasError(t, resp.Diagnostics)
		expectedErr := fmt.Sprintf("expected state file of total %d bytes, received %d bytes", incorrectLength, correctLength)
		if resp.Diagnostics.Err().Error() != expectedErr {
			t.Fatalf("expected error diagnostic %q, but got: %q", expectedErr, resp.Diagnostics.Err())
		}
		if len(resp.Bytes) != 0 {
			t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes))
		}
	})

	t.Run("an error diagnostic is returned when store type does not exist", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// In this scenario the method returns before the call to the
		// ReadStateBytes RPC, so no mocking needed

		badStoreType := "doesnt_exist"
		request := providers.ReadStateBytesRequest{
			TypeName: badStoreType,
			StateId:  backend.DefaultStateName,
		}

		// Act
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiagsHasError(t, resp.Diagnostics)
		expectedErr := fmt.Sprintf("unknown state store type %q", badStoreType)
		if resp.Diagnostics.Err().Error() != expectedErr {
			t.Fatalf("expected error diagnostic %q, but got: %q", expectedErr, resp.Diagnostics.Err())
		}
		if len(resp.Bytes) != 0 {
			t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes))
		}
	})

	t.Run("error diagnostics from the provider are returned", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Call to ReadStateBytes
		// > Assert the arguments received
		// > Define the returned mock client
		mockReadBytesClient := mockReadStateBytesClient(t)

		expectedReq := &proto.ReadStateBytes_Request{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
		}
		client.EXPECT().ReadStateBytes(
			gomock.Any(),
			gomock.Eq(expectedReq),
			gomock.Any(),
		).Return(mockReadBytesClient, nil)

		// Define what will be returned by each call to Recv
		mockReadBytesClient.EXPECT().Recv().Return(&proto.ReadStateBytes_Response{
			Diagnostics: []*proto.Diagnostic{
				{
					Severity: proto.Diagnostic_ERROR,
					Summary:  "Error from test",
					Detail:   "This error is forced by the test case",
				},
			},
		}, nil)

		// Act
		request := providers.ReadStateBytesRequest{
			TypeName: expectedReq.TypeName,
			StateId:  expectedReq.StateId,
		}
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiagsHasError(t, resp.Diagnostics)
		expectedErr := "Error from test: This error is forced by the test case"
		if resp.Diagnostics.Err().Error() != expectedErr {
			t.Fatalf("expected error diagnostic %q, but got: %q", expectedErr, resp.Diagnostics.Err())
		}
		if len(resp.Bytes) != 0 {
			t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes))
		}
	})

	t.Run("warning diagnostics from the provider are returned", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Call to ReadStateBytes
		// > Assert the arguments received
		// > Define the returned mock client
		mockReadBytesClient := mockReadStateBytesClient(t)

		expectedReq := &proto.ReadStateBytes_Request{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
		}
		client.EXPECT().ReadStateBytes(
			gomock.Any(),
			gomock.Eq(expectedReq),
			gomock.Any(),
		).Return(mockReadBytesClient, nil)

		// Define what will be returned by each call to Recv
		chunk := "hello world"
		totalLength := len(chunk)
		mockResp := map[int]struct {
			resp *proto.ReadStateBytes_Response
			err  error
		}{
			0: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunk),
					TotalLength: int64(totalLength),
					Range: &proto.StateRange{
						Start: 0,
						End:   int64(len(chunk)) - 1,
					},
					Diagnostics: []*proto.Diagnostic{
						{
							Severity: proto.Diagnostic_WARNING,
							Summary:  "Warning from test",
							Detail:   "This warning is forced by the test case",
						},
					},
				},
				err: nil,
			},
			1: {
				resp: &proto.ReadStateBytes_Response{},
				err:  io.EOF,
			},
		}
		var count int
		mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_Response, error) {
			ret := mockResp[count]
			count++
			return ret.resp, ret.err
		}).Times(2)

		// There will be a call to CloseSend to close the stream
		mockReadBytesClient.EXPECT().CloseSend().Return(nil).Times(1)

		// Act
		request := providers.ReadStateBytesRequest{
			TypeName: expectedReq.TypeName,
			StateId:  expectedReq.StateId,
		}
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiags(t, resp.Diagnostics)
		expectedWarn := "Warning from test: This warning is forced by the test case"
		if resp.Diagnostics.ErrWithWarnings().Error() != expectedWarn {
			t.Fatalf("expected warning diagnostic %q, but got: %q", expectedWarn, resp.Diagnostics.ErrWithWarnings().Error())
		}
		if len(resp.Bytes) == 0 {
			t.Fatal("expected data to included despite warnings, but got no bytes")
		}
	})

	t.Run("when reading data, grpc errors are surfaced via diagnostics", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Call to ReadStateBytes
		// > Assert the arguments received
		// > Define the returned mock client
		mockClient := mockReadStateBytesClient(t)
		expectedReq := &proto.ReadStateBytes_Request{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
		}
		client.EXPECT().ReadStateBytes(
			gomock.Any(),
			gomock.Eq(expectedReq),
			gomock.Any(),
		).Return(mockClient, nil)

		mockError := errors.New("grpc error forced in test")
		mockClient.EXPECT().Recv().Return(&proto.ReadStateBytes_Response{}, mockError)

		// Act
		request := providers.ReadStateBytesRequest{
			TypeName: expectedReq.TypeName,
			StateId:  expectedReq.StateId,
		}
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiagsHasError(t, resp.Diagnostics)
		wantErr := fmt.Sprintf("Plugin error: The plugin returned an unexpected error from plugin6.(*GRPCProvider).ReadStateBytes: %s", mockError)
		if resp.Diagnostics.Err().Error() != wantErr {
			t.Fatalf("expected error diagnostic %q, but got: %q", wantErr, resp.Diagnostics.Err())
		}
		if len(resp.Bytes) != 0 {
			t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes))
		}
	})

	t.Run("when closing the stream, grpc errors are surfaced via diagnostics", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Call to ReadStateBytes
		// > Assert the arguments received
		// > Define the returned mock client
		mockClient := mockReadStateBytesClient(t)
		expectedReq := &proto.ReadStateBytes_Request{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
		}
		client.EXPECT().ReadStateBytes(
			gomock.Any(),
			gomock.Eq(expectedReq),
			gomock.Any(),
		).Return(mockClient, nil)

		// Sufficient mocking of Recv to get to the call to CloseSend
		mockClient.EXPECT().Recv().Return(&proto.ReadStateBytes_Response{}, io.EOF)

		// Force a gRPC error from CloseSend
		mockError := errors.New("grpc error forced in test")
		mockClient.EXPECT().CloseSend().Return(mockError).Times(1)

		// Act
		request := providers.ReadStateBytesRequest{
			TypeName: expectedReq.TypeName,
			StateId:  expectedReq.StateId,
		}
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiagsHasError(t, resp.Diagnostics)
		wantErr := fmt.Sprintf("Plugin error: The plugin returned an unexpected error from plugin6.(*GRPCProvider).ReadStateBytes: %s", mockError)
		if resp.Diagnostics.Err().Error() != wantErr {
			t.Fatalf("expected error diagnostic %q, but got: %q", wantErr, resp.Diagnostics.Err())
		}
		if len(resp.Bytes) != 0 {
			t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes))
		}
	})

	t.Run("when reading the data, warnings are raised when chunk sizes mismatch", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", 5)

		// Call to ReadStateBytes
		// > Assert the arguments received
		// > Define the returned mock client
		mockReadBytesClient := mockReadStateBytesClient(t)

		expectedReq := &proto.ReadStateBytes_Request{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
		}
		client.EXPECT().ReadStateBytes(
			gomock.Any(),
			gomock.Eq(expectedReq),
			gomock.Any(),
		).Return(mockReadBytesClient, nil)

		// Define what will be returned by each call to Recv
		chunk := "helloworld"
		totalLength := len(chunk)
		mockResp := map[int]struct {
			resp *proto.ReadStateBytes_Response
			err  error
		}{
			0: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunk[:4]),
					TotalLength: int64(totalLength),
					Range: &proto.StateRange{
						Start: 0,
						End:   3,
					},
					Diagnostics: []*proto.Diagnostic{},
				},
				err: nil,
			},
			1: {
				resp: &proto.ReadStateBytes_Response{
					Bytes:       []byte(chunk[4:]),
					TotalLength: int64(totalLength),
					Range: &proto.StateRange{
						Start: 4,
						End:   9,
					},
					Diagnostics: []*proto.Diagnostic{},
				},
				err: nil,
			},
			2: {
				resp: &proto.ReadStateBytes_Response{},
				err:  io.EOF,
			},
		}
		var count int
		mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_Response, error) {
			ret := mockResp[count]
			count++
			return ret.resp, ret.err
		}).Times(3)

		// There will be a call to CloseSend to close the stream
		mockReadBytesClient.EXPECT().CloseSend().Return(nil).Times(1)

		// Act
		request := providers.ReadStateBytesRequest{
			TypeName: expectedReq.TypeName,
			StateId:  expectedReq.StateId,
		}
		resp := p.ReadStateBytes(request)

		// Assert returned values
		checkDiags(t, resp.Diagnostics)
		expectedWarn := `2 warnings:

- Unexpected size of chunk received: Unexpected chunk of size 4 was received, expected 5; this is a bug in the provider mock_store - please report it there
- Unexpected size of last chunk received: Last chunk exceeded agreed size, expected 5, given 6; this is a bug in the provider mock_store - please report it there`
		if resp.Diagnostics.ErrWithWarnings().Error() != expectedWarn {
			t.Fatalf("expected warning diagnostic %q, but got: %q", expectedWarn, resp.Diagnostics.ErrWithWarnings().Error())
		}
		if len(resp.Bytes) != 10 {
			t.Fatalf("expected 10 bytes to be read despite warnings, but got %d", len(resp.Bytes))
		}
	})
}

func TestGRPCProvider_WriteStateBytes(t *testing.T) {
	chunkSize := 4 << 20 // 4MB

	t.Run("data smaller than the chunk size is sent in one write action", func(t *testing.T) {
		// Less than 4MB
		data := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod" +
			"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" +
			" exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor" +
			" in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint" +
			" occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Assert there will be a call to WriteStateBytes
		// & make it return the mock client
		mockWriteClient := mockWriteStateBytesClient(t)
		client.EXPECT().WriteStateBytes(
			gomock.Any(),
			gomock.Any(),
		).Return(mockWriteClient, nil)

		// Spy on arguments passed to the Send method of the client
		//
		// We expect 1 call to Send as the total data
		// is less than the chunk size
		expectedReq := &proto.WriteStateBytes_RequestChunk{
			Meta: &proto.RequestChunkMeta{
				TypeName: "mock_store",
				StateId:  backend.DefaultStateName,
			},
			Bytes:       data,
			TotalLength: int64(len(data)),
			Range: &proto.StateRange{
				Start: 0,
				End:   int64(len(data)) - 1,
			},
		}
		mockWriteClient.EXPECT().Send(gomock.Eq(expectedReq)).Times(1).Return(nil)
		mockWriteClient.EXPECT().CloseAndRecv().Times(1).Return(&proto.WriteStateBytes_Response{}, nil)

		// Act
		request := providers.WriteStateBytesRequest{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
			Bytes:    data,
		}
		resp := p.WriteStateBytes(request)

		// Assert returned values
		checkDiags(t, resp.Diagnostics)
	})

	t.Run("data larger than the chunk size is sent in multiple write actions", func(t *testing.T) {
		// Make a buffer that can contain 10 bytes more than the 4MB chunk size
		chunkSize := 4 * 1_000_000
		dataBuff := bytes.NewBuffer(make([]byte, 0, chunkSize+10))
		dataBuffCopy := bytes.NewBuffer(make([]byte, 0, chunkSize+10))
		for i := 0; i < (chunkSize + 10); i++ {
			dataBuff.WriteByte(63)     // We're making 4MB + 10 bytes of question marks because why not
			dataBuffCopy.WriteByte(63) // Used to make assertions
		}
		data := dataBuff.Bytes()
		dataFirstChunk := dataBuffCopy.Next(chunkSize)  // First write will have a full chunk
		dataSecondChunk := dataBuffCopy.Next(chunkSize) // This will be the extra 10 bytes

		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Assert there will be a call to WriteStateBytes
		// & make it return the mock client
		mockWriteClient := mockWriteStateBytesClient(t)
		client.EXPECT().WriteStateBytes(
			gomock.Any(),
			gomock.Any(),
		).Return(mockWriteClient, nil)

		// Spy on arguments passed to the Send method because data
		// is written via separate chunks and separate calls to Send.
		//
		// We expect 2 calls to Send as the total data
		// is 10 bytes larger than the chunk size
		req1 := &proto.WriteStateBytes_RequestChunk{
			Meta: &proto.RequestChunkMeta{
				TypeName: "mock_store",
				StateId:  backend.DefaultStateName,
			},
			Bytes:       dataFirstChunk,
			TotalLength: int64(len(data)),
			Range: &proto.StateRange{
				Start: 0,
				End:   int64(chunkSize) - 1,
			},
		}
		req2 := &proto.WriteStateBytes_RequestChunk{
			Meta:        nil,
			Bytes:       dataSecondChunk,
			TotalLength: int64(len(data)),
			Range: &proto.StateRange{
				Start: int64(chunkSize),
				End:   int64(chunkSize+10) - 1,
			},
		}
		mockWriteClient.EXPECT().Send(gomock.AnyOf(req1, req2)).Times(2).Return(nil)
		mockWriteClient.EXPECT().CloseAndRecv().Times(1).Return(&proto.WriteStateBytes_Response{}, nil)

		// Act
		request := providers.WriteStateBytesRequest{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
			Bytes:    data,
		}
		resp := p.WriteStateBytes(request)

		// Assert returned values
		checkDiags(t, resp.Diagnostics)
	})

	t.Run("when writing data, grpc errors are surfaced via diagnostics", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Assert there will be a call to WriteStateBytes
		// & make it return the mock client
		mockWriteClient := mockWriteStateBytesClient(t)
		client.EXPECT().WriteStateBytes(
			gomock.Any(),
			gomock.Any(),
		).Return(mockWriteClient, nil)

		mockError := errors.New("grpc error forced in test")
		mockWriteClient.EXPECT().Send(gomock.Any()).Return(mockError)

		// Act
		request := providers.WriteStateBytesRequest{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
			Bytes:    []byte("helloworld"),
		}
		resp := p.WriteStateBytes(request)

		// Assert returned values
		checkDiagsHasError(t, resp.Diagnostics)
		wantErr := fmt.Sprintf("Plugin error: The plugin returned an unexpected error from plugin6.(*GRPCProvider).WriteStateBytes: %s", mockError)
		if resp.Diagnostics.Err().Error() != wantErr {
			t.Fatalf("unexpected error, wanted %q, got: %s", wantErr, resp.Diagnostics.Err())
		}
	})

	t.Run("error diagnostics from the provider when closing the connection are returned", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Assert there will be a call to WriteStateBytes
		// & make it return the mock client
		mockWriteClient := mockWriteStateBytesClient(t)
		client.EXPECT().WriteStateBytes(
			gomock.Any(),
			gomock.Any(),
		).Return(mockWriteClient, nil)

		data := []byte("helloworld")
		mockReq := &proto.WriteStateBytes_RequestChunk{
			Meta: &proto.RequestChunkMeta{
				TypeName: "mock_store",
				StateId:  backend.DefaultStateName,
			},
			Bytes:       data,
			TotalLength: int64(len(data)),
			Range: &proto.StateRange{
				Start: 0,
				End:   int64(len(data)) - 1,
			},
		}
		mockResp := &proto.WriteStateBytes_Response{
			Diagnostics: []*proto.Diagnostic{
				{
					Severity: proto.Diagnostic_ERROR,
					Summary:  "Error from test mock",
					Detail:   "This error is returned from the test mock",
				},
			},
		}
		mockWriteClient.EXPECT().Send(gomock.Eq(mockReq)).Times(1).Return(nil)
		mockWriteClient.EXPECT().CloseAndRecv().Times(1).Return(mockResp, nil)

		// Act
		request := providers.WriteStateBytesRequest{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
			Bytes:    data,
		}
		resp := p.WriteStateBytes(request)

		// Assert returned values
		checkDiagsHasError(t, resp.Diagnostics)
		if resp.Diagnostics.Err().Error() != "Error from test mock: This error is returned from the test mock" {
			t.Fatal()
		}
	})

	t.Run("warning diagnostics from the provider when closing the connection are returned", func(t *testing.T) {
		client := mockProviderClient(t)
		p := &GRPCProvider{
			client: client,
			ctx:    context.Background(),
		}
		p.SetStateStoreChunkSize("mock_store", chunkSize)

		// Assert there will be a call to WriteStateBytes
		// & make it return the mock client
		mockWriteClient := mockWriteStateBytesClient(t)
		client.EXPECT().WriteStateBytes(
			gomock.Any(),
			gomock.Any(),
		).Return(mockWriteClient, nil)

		data := []byte("helloworld")
		mockReq := &proto.WriteStateBytes_RequestChunk{
			Meta: &proto.RequestChunkMeta{
				TypeName: "mock_store",
				StateId:  backend.DefaultStateName,
			},
			Bytes:       data,
			TotalLength: int64(len(data)),
			Range: &proto.StateRange{
				Start: 0,
				End:   int64(len(data)) - 1,
			},
		}
		mockResp := &proto.WriteStateBytes_Response{
			Diagnostics: []*proto.Diagnostic{
				{
					Severity: proto.Diagnostic_WARNING,
					Summary:  "Warning from test mock",
					Detail:   "This warning is returned from the test mock",
				},
			},
		}
		mockWriteClient.EXPECT().Send(gomock.Eq(mockReq)).Times(1).Return(nil)
		mockWriteClient.EXPECT().CloseAndRecv().Times(1).Return(mockResp, nil)

		// Act
		request := providers.WriteStateBytesRequest{
			TypeName: "mock_store",
			StateId:  backend.DefaultStateName,
			Bytes:    data,
		}
		resp := p.WriteStateBytes(request)

		// Assert returned values
		checkDiags(t, resp.Diagnostics)
		if resp.Diagnostics.ErrWithWarnings().Error() != "Warning from test mock: This warning is returned from the test mock" {
			t.Fatal()
		}
	})
}

func TestGRPCProvider_GenerateResourceConfig(t *testing.T) {
	client := mockProviderClient(t)
	p := &GRPCProvider{
		client: client,
	}
	client.EXPECT().GenerateResourceConfig(
		gomock.Any(),
		gomock.Cond[any](func(x any) bool {
			req := x.(*proto.GenerateResourceConfig_Request)
			if req.TypeName != "resource" {
				return false
			}
			if req.State == nil {
				t.Log("GenerateResourceConfig state is nil")
				return false
			}
			return true
		}),
	).Return(&proto.GenerateResourceConfig_Response{
		Config: &proto.DynamicValue{
			Msgpack: []byte("\x81\xa4attr\xa3bar"),
		},
	}, nil)
	resp := p.GenerateResourceConfig(providers.GenerateResourceConfigRequest{
		TypeName: "resource",
		State: cty.ObjectVal(map[string]cty.Value{
			"computed": cty.StringVal("computed"),
			"attr":     cty.StringVal("foo"),
		}),
	})
	checkDiags(t, resp.Diagnostics)
}
