mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Signed-off-by: Christian Mesh <christianmesh1@gmail.com> Co-authored-by: Martin Atkins <mart@degeneration.co.uk>
1293 lines
38 KiB
Go
1293 lines
38 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/msgpack"
|
|
"go.uber.org/mock/gomock"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
"github.com/opentofu/opentofu/internal/legacy/hcl2shim"
|
|
mockproto "github.com/opentofu/opentofu/internal/plugin/mock_proto"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
proto "github.com/opentofu/opentofu/internal/tfplugin5"
|
|
)
|
|
|
|
var _ providers.Interface = (*GRPCProvider)(nil)
|
|
|
|
func mutateSchemaResponse(response *proto.GetProviderSchema_Response, mut ...func(schemaResponse *proto.GetProviderSchema_Response)) *proto.GetProviderSchema_Response {
|
|
for _, f := range mut {
|
|
f(response)
|
|
}
|
|
return response
|
|
}
|
|
|
|
func addAttributeToResource(resourceName string, attr *proto.Schema_Attribute) func(response *proto.GetProviderSchema_Response) {
|
|
return func(schemaResponse *proto.GetProviderSchema_Response) {
|
|
schemaResponse.ResourceSchemas[resourceName].Block.Attributes = append(schemaResponse.ResourceSchemas[resourceName].Block.Attributes, attr)
|
|
}
|
|
}
|
|
|
|
func mockProviderClient(t *testing.T) *mockproto.MockProviderClient {
|
|
return mockProviderClientWithSchema(t, providerProtoSchema())
|
|
}
|
|
|
|
func mockProviderClientWithSchema(t *testing.T, schema *proto.GetProviderSchema_Response) *mockproto.MockProviderClient {
|
|
ctrl := gomock.NewController(t)
|
|
client := mockproto.NewMockProviderClient(ctrl)
|
|
|
|
// we always need a GetSchema method
|
|
client.EXPECT().GetSchema(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(schema, nil)
|
|
|
|
return client
|
|
}
|
|
|
|
func newGRPCProvider(client proto.ProviderClient) *GRPCProvider {
|
|
return &GRPCProvider{
|
|
client: client,
|
|
SchemaCache: providers.NewSchemaCache(),
|
|
}
|
|
}
|
|
|
|
func checkDiags(t *testing.T, d tfdiags.Diagnostics) {
|
|
t.Helper()
|
|
if d.HasErrors() {
|
|
t.Fatal(d.Err())
|
|
}
|
|
}
|
|
|
|
// checkDiagsHasError ensures error diagnostics are present or fails the test.
|
|
func checkDiagsHasError(t *testing.T, d tfdiags.Diagnostics) {
|
|
t.Helper()
|
|
|
|
if !d.HasErrors() {
|
|
t.Fatal("expected error diagnostics")
|
|
}
|
|
}
|
|
|
|
func providerProtoSchema() *proto.GetProviderSchema_Response {
|
|
return &proto.GetProviderSchema_Response{
|
|
Provider: &proto.Schema{
|
|
Block: &proto.Schema_Block{
|
|
Attributes: []*proto.Schema_Attribute{
|
|
{
|
|
Name: "attr",
|
|
Type: []byte(`"string"`),
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ResourceSchemas: map[string]*proto.Schema{
|
|
"resource": &proto.Schema{
|
|
Version: 1,
|
|
Block: &proto.Schema_Block{
|
|
Attributes: []*proto.Schema_Attribute{
|
|
{
|
|
Name: "attr",
|
|
Type: []byte(`"string"`),
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
DataSourceSchemas: map[string]*proto.Schema{
|
|
"data": &proto.Schema{
|
|
Version: 1,
|
|
Block: &proto.Schema_Block{
|
|
Attributes: []*proto.Schema_Attribute{
|
|
{
|
|
Name: "attr",
|
|
Type: []byte(`"string"`),
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
EphemeralResourceSchemas: map[string]*proto.Schema{
|
|
"eph": {
|
|
Version: 1,
|
|
Block: &proto.Schema_Block{
|
|
Attributes: []*proto.Schema_Attribute{
|
|
{
|
|
Name: "attr",
|
|
Type: []byte(`"string"`),
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Functions: map[string]*proto.Function{
|
|
"fn": &proto.Function{
|
|
Parameters: []*proto.Function_Parameter{{
|
|
Name: "par_a",
|
|
Type: []byte(`"string"`),
|
|
AllowNullValue: false,
|
|
AllowUnknownValues: false,
|
|
}},
|
|
VariadicParameter: &proto.Function_Parameter{
|
|
Name: "par_var",
|
|
Type: []byte(`"string"`),
|
|
AllowNullValue: true,
|
|
AllowUnknownValues: false,
|
|
},
|
|
Return: &proto.Function_Return{
|
|
Type: []byte(`"string"`),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_GetSchema(t *testing.T) {
|
|
p := newGRPCProvider(mockProviderClient(t))
|
|
|
|
resp := p.GetProviderSchema(t.Context())
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
{ // check ephemeral attribute of the schema blocks
|
|
if !resp.Provider.Block.Ephemeral {
|
|
t.Errorf("provider.Block.Ephemeral meant to be true")
|
|
}
|
|
checkResources := func(t *testing.T, r map[string]providers.Schema, want bool) {
|
|
for typ, schema := range r {
|
|
if schema.Block.Ephemeral != want {
|
|
t.Errorf("expected resource %q to have ephemeral as %t", typ, want)
|
|
}
|
|
}
|
|
}
|
|
checkResources(t, resp.ResourceTypes, false)
|
|
checkResources(t, resp.DataSources, false)
|
|
checkResources(t, resp.EphemeralResources, true)
|
|
}
|
|
}
|
|
|
|
// Ensure that gRPC errors are returned early.
|
|
// Reference: https://github.com/hashicorp/terraform/issues/31047
|
|
func TestGRPCProvider_GetSchema_GRPCError(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
client := mockproto.NewMockProviderClient(ctrl)
|
|
|
|
client.EXPECT().GetSchema(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.GetProviderSchema_Response{}, fmt.Errorf("test error"))
|
|
|
|
p := newGRPCProvider(client)
|
|
|
|
resp := p.GetProviderSchema(t.Context())
|
|
|
|
checkDiagsHasError(t, resp.Diagnostics)
|
|
}
|
|
|
|
func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
client := mockproto.NewMockProviderClient(ctrl)
|
|
|
|
cache := providers.NewSchemaCache()
|
|
|
|
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
|
|
|
client.EXPECT().GetSchema(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Times(1).Return(&proto.GetProviderSchema_Response{
|
|
Provider: mockedProviderResponse,
|
|
ServerCapabilities: &proto.ServerCapabilities{GetProviderSchemaOptional: true},
|
|
}, nil)
|
|
|
|
// Run GetProviderTwice, expect GetSchema to be called once
|
|
// Re-initialize the provider before each run to avoid usage of the local cache
|
|
p := newGRPCProvider(client)
|
|
p.SchemaCache = cache
|
|
resp := p.GetProviderSchema(t.Context())
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) {
|
|
t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version))
|
|
}
|
|
|
|
p = newGRPCProvider(client)
|
|
p.SchemaCache = cache
|
|
resp = p.GetProviderSchema(t.Context())
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) {
|
|
t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
client := mockproto.NewMockProviderClient(ctrl)
|
|
|
|
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
|
|
|
client.EXPECT().GetSchema(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Times(2).Return(&proto.GetProviderSchema_Response{
|
|
Provider: mockedProviderResponse,
|
|
ServerCapabilities: &proto.ServerCapabilities{GetProviderSchemaOptional: false},
|
|
}, nil)
|
|
|
|
// Run GetProviderTwice, expect GetSchema to be called once
|
|
// Re-initialize the provider before each run to avoid usage of the local cache
|
|
p := newGRPCProvider(client)
|
|
resp := p.GetProviderSchema(t.Context())
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) {
|
|
t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version))
|
|
}
|
|
|
|
p = newGRPCProvider(client)
|
|
resp = p.GetProviderSchema(t.Context())
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
if !cmp.Equal(resp.Provider.Version, mockedProviderResponse.Version) {
|
|
t.Fatal(cmp.Diff(resp.Provider.Version, mockedProviderResponse.Version))
|
|
}
|
|
}
|
|
|
|
// Ensure that provider error diagnostics are returned early.
|
|
// Reference: https://github.com/hashicorp/terraform/issues/31047
|
|
func TestGRPCProvider_GetSchema_ResponseErrorDiagnostic(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
client := mockproto.NewMockProviderClient(ctrl)
|
|
|
|
client.EXPECT().GetSchema(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.GetProviderSchema_Response{
|
|
Diagnostics: []*proto.Diagnostic{
|
|
{
|
|
Severity: proto.Diagnostic_ERROR,
|
|
Summary: "error summary",
|
|
Detail: "error detail",
|
|
},
|
|
},
|
|
// Trigger potential panics
|
|
Provider: &proto.Schema{},
|
|
}, nil)
|
|
|
|
p := newGRPCProvider(client)
|
|
|
|
resp := p.GetProviderSchema(t.Context())
|
|
|
|
checkDiagsHasError(t, resp.Diagnostics)
|
|
}
|
|
|
|
func TestGRPCProvider_PrepareProviderConfig(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().PrepareProviderConfig(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.PrepareProviderConfig_Response{}, nil)
|
|
|
|
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
|
|
resp := p.ValidateProviderConfig(t.Context(), providers.ValidateProviderConfigRequest{Config: cfg})
|
|
checkDiags(t, resp.Diagnostics)
|
|
}
|
|
|
|
func TestGRPCProvider_ValidateResourceConfig(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ValidateResourceTypeConfig(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ValidateResourceTypeConfig_Response{}, nil)
|
|
|
|
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
|
|
resp := p.ValidateResourceConfig(t.Context(), providers.ValidateResourceConfigRequest{
|
|
TypeName: "resource",
|
|
Config: cfg,
|
|
})
|
|
checkDiags(t, resp.Diagnostics)
|
|
}
|
|
|
|
func TestGRPCProvider_ValidateDataSourceConfig(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ValidateDataSourceConfig(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ValidateDataSourceConfig_Response{}, nil)
|
|
|
|
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
|
|
resp := p.ValidateDataResourceConfig(t.Context(), providers.ValidateDataResourceConfigRequest{
|
|
TypeName: "data",
|
|
Config: cfg,
|
|
})
|
|
checkDiags(t, resp.Diagnostics)
|
|
}
|
|
|
|
func TestGRPCProvider_ValidateEphemeralResourceConfig(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ValidateEphemeralResourceConfig(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ValidateEphemeralResourceConfig_Response{}, nil)
|
|
|
|
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
|
|
resp := p.ValidateEphemeralConfig(t.Context(), providers.ValidateEphemeralConfigRequest{
|
|
TypeName: "eph",
|
|
Config: cfg,
|
|
})
|
|
checkDiags(t, resp.Diagnostics)
|
|
}
|
|
|
|
func TestGRPCProvider_UpgradeResourceState(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().UpgradeResourceState(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.UpgradeResourceState_Response{
|
|
UpgradedState: &proto.DynamicValue{
|
|
Msgpack: []byte("\x81\xa4attr\xa3bar"),
|
|
},
|
|
}, nil)
|
|
|
|
resp := p.UpgradeResourceState(t.Context(), providers.UpgradeResourceStateRequest{
|
|
TypeName: "resource",
|
|
Version: 0,
|
|
RawStateJSON: []byte(`{"old_attr":"bar"}`),
|
|
})
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expected := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_UpgradeResourceStateJSON(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().UpgradeResourceState(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.UpgradeResourceState_Response{
|
|
UpgradedState: &proto.DynamicValue{
|
|
Json: []byte(`{"attr":"bar"}`),
|
|
},
|
|
}, nil)
|
|
|
|
resp := p.UpgradeResourceState(t.Context(), providers.UpgradeResourceStateRequest{
|
|
TypeName: "resource",
|
|
Version: 0,
|
|
RawStateJSON: []byte(`{"old_attr":"bar"}`),
|
|
})
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expected := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expected, resp.UpgradedState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_UpgradeResourceStateWithWriteOnlyReturned(t *testing.T) {
|
|
client := mockProviderClientWithSchema(t, mutateSchemaResponse(providerProtoSchema(), addAttributeToResource("resource", &proto.Schema_Attribute{
|
|
Name: "write_only_attr",
|
|
Type: []byte(`"string"`),
|
|
Optional: true,
|
|
WriteOnly: true,
|
|
})))
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().UpgradeResourceState(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).DoAndReturn(func(_ context.Context, _ *proto.UpgradeResourceState_Request, _ ...grpc.CallOption) (*proto.UpgradeResourceState_Response, error) {
|
|
b, err := msgpack.Marshal(
|
|
cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("bar"), "write_only_attr": cty.StringVal("val")}),
|
|
cty.Object(map[string]cty.Type{"attr": cty.String, "write_only_attr": cty.String}),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &proto.UpgradeResourceState_Response{
|
|
UpgradedState: &proto.DynamicValue{
|
|
Msgpack: b,
|
|
},
|
|
}, nil
|
|
})
|
|
|
|
resp := p.UpgradeResourceState(t.Context(), providers.UpgradeResourceStateRequest{
|
|
TypeName: "resource",
|
|
Version: 0,
|
|
RawStateJSON: []byte(`{"attr":"bar"}`),
|
|
})
|
|
checkDiagsHasError(t, resp.Diagnostics)
|
|
|
|
expectedErr := `Resource type "resource" returned an actual value for the write-only attribute ".write_only_attr" while it is meant to be nil. This is an issue in the provider SDK.`
|
|
if gotErr := resp.Diagnostics[0].Description().Detail; expectedErr != gotErr {
|
|
t.Errorf("the expected error is not the same with the one returned.\nexpected: %s\ngot: %s", expectedErr, gotErr)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_MoveResourceState(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().MoveResourceState(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.MoveResourceState_Response{
|
|
TargetState: &proto.DynamicValue{
|
|
Msgpack: []byte("\x81\xa4attr\xa3bar"),
|
|
},
|
|
TargetPrivate: []byte(`{"meta": "data"}`),
|
|
}, nil)
|
|
|
|
resp := p.MoveResourceState(t.Context(), providers.MoveResourceStateRequest{
|
|
SourceTypeName: "resource_old",
|
|
SourceSchemaVersion: 0,
|
|
TargetTypeName: "resource",
|
|
})
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expectedState := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
expectedPrivate := []byte(`{"meta": "data"}`)
|
|
|
|
if !cmp.Equal(expectedState, resp.TargetState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expectedState, resp.TargetState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
if !bytes.Equal(expectedPrivate, resp.TargetPrivate) {
|
|
t.Fatalf("expected %q, got %q", expectedPrivate, resp.TargetPrivate)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_MoveResourceStateReturnsWriteOnlyValue(t *testing.T) {
|
|
client := mockProviderClientWithSchema(t, mutateSchemaResponse(providerProtoSchema(), addAttributeToResource("resource", &proto.Schema_Attribute{
|
|
Name: "write_only_attr",
|
|
Type: []byte(`"string"`),
|
|
Optional: true,
|
|
WriteOnly: true,
|
|
})))
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().MoveResourceState(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).DoAndReturn(func(_ context.Context, _ *proto.MoveResourceState_Request, _ ...grpc.CallOption) (*proto.MoveResourceState_Response, error) {
|
|
b, err := msgpack.Marshal(
|
|
cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("bar"), "write_only_attr": cty.StringVal("val")}),
|
|
cty.Object(map[string]cty.Type{"attr": cty.String, "write_only_attr": cty.String}),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &proto.MoveResourceState_Response{
|
|
TargetState: &proto.DynamicValue{
|
|
Msgpack: b,
|
|
},
|
|
}, nil
|
|
})
|
|
|
|
resp := p.MoveResourceState(t.Context(), providers.MoveResourceStateRequest{
|
|
SourceTypeName: "resource_old",
|
|
SourceSchemaVersion: 0,
|
|
TargetTypeName: "resource",
|
|
})
|
|
checkDiagsHasError(t, resp.Diagnostics)
|
|
|
|
expectedErr := `Resource type "resource" returned an actual value for the write-only attribute ".write_only_attr" while it is meant to be nil. This is an issue in the provider SDK.`
|
|
if gotErr := resp.Diagnostics[0].Description().Detail; expectedErr != gotErr {
|
|
t.Errorf("the expected error is not the same with the one returned.\nexpected: %s\ngot: %s", expectedErr, gotErr)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_Configure(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().Configure(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.Configure_Response{}, nil)
|
|
|
|
resp := p.ConfigureProvider(t.Context(), providers.ConfigureProviderRequest{
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
})
|
|
checkDiags(t, resp.Diagnostics)
|
|
}
|
|
|
|
func TestGRPCProvider_Stop(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
client := mockproto.NewMockProviderClient(ctrl)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().Stop(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.Stop_Response{}, nil)
|
|
|
|
err := p.Stop(t.Context())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ReadResource(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ReadResource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ReadResource_Response{
|
|
NewState: &proto.DynamicValue{
|
|
Msgpack: []byte("\x81\xa4attr\xa3bar"),
|
|
},
|
|
}, nil)
|
|
|
|
resp := p.ReadResource(t.Context(), providers.ReadResourceRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expected := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ReadResourceJSON(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ReadResource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ReadResource_Response{
|
|
NewState: &proto.DynamicValue{
|
|
Json: []byte(`{"attr":"bar"}`),
|
|
},
|
|
}, nil)
|
|
|
|
resp := p.ReadResource(t.Context(), providers.ReadResourceRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expected := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ReadResourceReturnsWriteOnlyValue(t *testing.T) {
|
|
client := mockProviderClientWithSchema(t, mutateSchemaResponse(providerProtoSchema(), addAttributeToResource("resource", &proto.Schema_Attribute{
|
|
Name: "write_only_attr",
|
|
Type: []byte(`"string"`),
|
|
Optional: true,
|
|
WriteOnly: true,
|
|
})))
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ReadResource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).DoAndReturn(func(_ context.Context, _ *proto.ReadResource_Request, opts ...grpc.CallOption) (*proto.ReadResource_Response, error) {
|
|
b, err := msgpack.Marshal(
|
|
cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("bar"), "write_only_attr": cty.StringVal("val")}),
|
|
cty.Object(map[string]cty.Type{"attr": cty.String, "write_only_attr": cty.String}),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &proto.ReadResource_Response{
|
|
NewState: &proto.DynamicValue{
|
|
Msgpack: b,
|
|
},
|
|
}, nil
|
|
})
|
|
|
|
resp := p.ReadResource(t.Context(), providers.ReadResourceRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
"write_only_attr": cty.NullVal(cty.String),
|
|
}),
|
|
})
|
|
|
|
checkDiagsHasError(t, resp.Diagnostics)
|
|
|
|
expectedErr := `Resource type "resource" returned an actual value for the write-only attribute ".write_only_attr" while it is meant to be nil. This is an issue in the provider SDK.`
|
|
if gotErr := resp.Diagnostics[0].Description().Detail; expectedErr != gotErr {
|
|
t.Errorf("the expected error is not the same with the one returned.\nexpected: %s\ngot: %s", expectedErr, gotErr)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ReadEmptyJSON(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ReadResource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ReadResource_Response{
|
|
NewState: &proto.DynamicValue{
|
|
Json: []byte(``),
|
|
},
|
|
}, nil)
|
|
|
|
obj := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
})
|
|
resp := p.ReadResource(t.Context(), providers.ReadResourceRequest{
|
|
TypeName: "resource",
|
|
PriorState: obj,
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expected := cty.NullVal(obj.Type())
|
|
|
|
if !cmp.Equal(expected, resp.NewState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expected, resp.NewState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_PlanResourceChange(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
expectedPrivate := []byte(`{"meta": "data"}`)
|
|
|
|
client.EXPECT().PlanResourceChange(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.PlanResourceChange_Response{
|
|
PlannedState: &proto.DynamicValue{
|
|
Msgpack: []byte("\x81\xa4attr\xa3bar"),
|
|
},
|
|
RequiresReplace: []*proto.AttributePath{
|
|
{
|
|
Steps: []*proto.AttributePath_Step{
|
|
{
|
|
Selector: &proto.AttributePath_Step_AttributeName{
|
|
AttributeName: "attr",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
PlannedPrivate: expectedPrivate,
|
|
}, nil)
|
|
|
|
resp := p.PlanResourceChange(t.Context(), providers.PlanResourceChangeRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
ProposedNewState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expectedState := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
|
|
expectedReplace := `[]cty.Path{cty.Path{cty.GetAttrStep{Name:"attr"}}}`
|
|
replace := fmt.Sprintf("%#v", resp.RequiresReplace)
|
|
if expectedReplace != replace {
|
|
t.Fatalf("expected %q, got %q", expectedReplace, replace)
|
|
}
|
|
|
|
if !bytes.Equal(expectedPrivate, resp.PlannedPrivate) {
|
|
t.Fatalf("expected %q, got %q", expectedPrivate, resp.PlannedPrivate)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_PlanResourceChangeJSON(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
expectedPrivate := []byte(`{"meta": "data"}`)
|
|
|
|
client.EXPECT().PlanResourceChange(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.PlanResourceChange_Response{
|
|
PlannedState: &proto.DynamicValue{
|
|
Json: []byte(`{"attr":"bar"}`),
|
|
},
|
|
RequiresReplace: []*proto.AttributePath{
|
|
{
|
|
Steps: []*proto.AttributePath_Step{
|
|
{
|
|
Selector: &proto.AttributePath_Step_AttributeName{
|
|
AttributeName: "attr",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
PlannedPrivate: expectedPrivate,
|
|
}, nil)
|
|
|
|
resp := p.PlanResourceChange(t.Context(), providers.PlanResourceChangeRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
ProposedNewState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expectedState := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expectedState, resp.PlannedState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
|
|
expectedReplace := `[]cty.Path{cty.Path{cty.GetAttrStep{Name:"attr"}}}`
|
|
replace := fmt.Sprintf("%#v", resp.RequiresReplace)
|
|
if expectedReplace != replace {
|
|
t.Fatalf("expected %q, got %q", expectedReplace, replace)
|
|
}
|
|
|
|
if !bytes.Equal(expectedPrivate, resp.PlannedPrivate) {
|
|
t.Fatalf("expected %q, got %q", expectedPrivate, resp.PlannedPrivate)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_PlanResourceChangeReturnsWriteOnlyValue(t *testing.T) {
|
|
client := mockProviderClientWithSchema(t, mutateSchemaResponse(providerProtoSchema(), addAttributeToResource("resource", &proto.Schema_Attribute{
|
|
Name: "write_only_attr",
|
|
Type: []byte(`"string"`),
|
|
Optional: true,
|
|
WriteOnly: true,
|
|
})))
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().PlanResourceChange(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).DoAndReturn(func(_ context.Context, _ *proto.PlanResourceChange_Request, opts ...grpc.CallOption) (*proto.PlanResourceChange_Response, error) {
|
|
b, err := msgpack.Marshal(
|
|
cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("bar"), "write_only_attr": cty.StringVal("val")}),
|
|
cty.Object(map[string]cty.Type{"attr": cty.String, "write_only_attr": cty.String}),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &proto.PlanResourceChange_Response{
|
|
PlannedState: &proto.DynamicValue{
|
|
Msgpack: b,
|
|
},
|
|
}, nil
|
|
})
|
|
resp := p.PlanResourceChange(t.Context(), providers.PlanResourceChangeRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
"write_only_attr": cty.NullVal(cty.String),
|
|
}),
|
|
ProposedNewState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
"write_only_attr": cty.NullVal(cty.String),
|
|
}),
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
"write_only_attr": cty.NullVal(cty.String),
|
|
}),
|
|
})
|
|
checkDiagsHasError(t, resp.Diagnostics)
|
|
expectedErr := `Resource type "resource" returned an actual value for the write-only attribute ".write_only_attr" while it is meant to be nil. This is an issue in the provider SDK.`
|
|
if gotErr := resp.Diagnostics[0].Description().Detail; expectedErr != gotErr {
|
|
t.Errorf("the expected error is not the same with the one returned.\nexpected: %s\ngot: %s", expectedErr, gotErr)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ApplyResourceChange(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
expectedPrivate := []byte(`{"meta": "data"}`)
|
|
|
|
client.EXPECT().ApplyResourceChange(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ApplyResourceChange_Response{
|
|
NewState: &proto.DynamicValue{
|
|
Msgpack: []byte("\x81\xa4attr\xa3bar"),
|
|
},
|
|
Private: expectedPrivate,
|
|
}, nil)
|
|
|
|
resp := p.ApplyResourceChange(t.Context(), providers.ApplyResourceChangeRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
PlannedPrivate: expectedPrivate,
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expectedState := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
|
|
if !bytes.Equal(expectedPrivate, resp.Private) {
|
|
t.Fatalf("expected %q, got %q", expectedPrivate, resp.Private)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ApplyResourceChangeJSON(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
expectedPrivate := []byte(`{"meta": "data"}`)
|
|
|
|
client.EXPECT().ApplyResourceChange(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ApplyResourceChange_Response{
|
|
NewState: &proto.DynamicValue{
|
|
Json: []byte(`{"attr":"bar"}`),
|
|
},
|
|
Private: expectedPrivate,
|
|
}, nil)
|
|
|
|
resp := p.ApplyResourceChange(t.Context(), providers.ApplyResourceChangeRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
PlannedPrivate: expectedPrivate,
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expectedState := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expectedState, resp.NewState, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
|
|
if !bytes.Equal(expectedPrivate, resp.Private) {
|
|
t.Fatalf("expected %q, got %q", expectedPrivate, resp.Private)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ApplyResourceChangeReturnsWriteOnlyValue(t *testing.T) {
|
|
client := mockProviderClientWithSchema(t, mutateSchemaResponse(providerProtoSchema(), addAttributeToResource("resource", &proto.Schema_Attribute{
|
|
Name: "write_only_attr",
|
|
Type: []byte(`"string"`),
|
|
Optional: true,
|
|
WriteOnly: true,
|
|
})))
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ApplyResourceChange(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).DoAndReturn(func(_ context.Context, _ *proto.ApplyResourceChange_Request, opts ...grpc.CallOption) (*proto.ApplyResourceChange_Response, error) {
|
|
b, err := msgpack.Marshal(
|
|
cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("bar"), "write_only_attr": cty.StringVal("val")}),
|
|
cty.Object(map[string]cty.Type{"attr": cty.String, "write_only_attr": cty.String}),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &proto.ApplyResourceChange_Response{
|
|
NewState: &proto.DynamicValue{
|
|
Msgpack: b,
|
|
},
|
|
}, nil
|
|
})
|
|
|
|
resp := p.ApplyResourceChange(t.Context(), providers.ApplyResourceChangeRequest{
|
|
TypeName: "resource",
|
|
PriorState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
"write_only_attr": cty.NullVal(cty.String),
|
|
}),
|
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
"write_only_attr": cty.NullVal(cty.String),
|
|
}),
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
"write_only_attr": cty.StringVal("foo"),
|
|
}),
|
|
})
|
|
checkDiagsHasError(t, resp.Diagnostics)
|
|
|
|
expectedErr := `Resource type "resource" returned an actual value for the write-only attribute ".write_only_attr" while it is meant to be nil. This is an issue in the provider SDK.`
|
|
if gotErr := resp.Diagnostics[0].Description().Detail; expectedErr != gotErr {
|
|
t.Errorf("the expected error is not the same with the one returned.\nexpected: %s\ngot: %s", expectedErr, gotErr)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ImportResourceState(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
expectedPrivate := []byte(`{"meta": "data"}`)
|
|
|
|
client.EXPECT().ImportResourceState(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ImportResourceState_Response{
|
|
ImportedResources: []*proto.ImportResourceState_ImportedResource{
|
|
{
|
|
TypeName: "resource",
|
|
State: &proto.DynamicValue{
|
|
Msgpack: []byte("\x81\xa4attr\xa3bar"),
|
|
},
|
|
Private: expectedPrivate,
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
resp := p.ImportResourceState(t.Context(), providers.ImportResourceStateRequest{
|
|
TypeName: "resource",
|
|
ID: "foo",
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expectedResource := providers.ImportedResource{
|
|
TypeName: "resource",
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
Private: expectedPrivate,
|
|
}
|
|
|
|
imported := resp.ImportedResources[0]
|
|
if !cmp.Equal(expectedResource, imported, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expectedResource, imported, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
func TestGRPCProvider_ImportResourceStateJSON(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
expectedPrivate := []byte(`{"meta": "data"}`)
|
|
|
|
client.EXPECT().ImportResourceState(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ImportResourceState_Response{
|
|
ImportedResources: []*proto.ImportResourceState_ImportedResource{
|
|
{
|
|
TypeName: "resource",
|
|
State: &proto.DynamicValue{
|
|
Json: []byte(`{"attr":"bar"}`),
|
|
},
|
|
Private: expectedPrivate,
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
resp := p.ImportResourceState(t.Context(), providers.ImportResourceStateRequest{
|
|
TypeName: "resource",
|
|
ID: "foo",
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expectedResource := providers.ImportedResource{
|
|
TypeName: "resource",
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
}),
|
|
Private: expectedPrivate,
|
|
}
|
|
|
|
imported := resp.ImportedResources[0]
|
|
if !cmp.Equal(expectedResource, imported, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expectedResource, imported, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ReadDataSource(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ReadDataSource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ReadDataSource_Response{
|
|
State: &proto.DynamicValue{
|
|
Msgpack: []byte("\x81\xa4attr\xa3bar"),
|
|
},
|
|
}, nil)
|
|
|
|
resp := p.ReadDataSource(t.Context(), providers.ReadDataSourceRequest{
|
|
TypeName: "data",
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expected := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expected, resp.State, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_ReadDataSourceJSON(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().ReadDataSource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.ReadDataSource_Response{
|
|
State: &proto.DynamicValue{
|
|
Json: []byte(`{"attr":"bar"}`),
|
|
},
|
|
}, nil)
|
|
|
|
resp := p.ReadDataSource(t.Context(), providers.ReadDataSourceRequest{
|
|
TypeName: "data",
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
expected := cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("bar"),
|
|
})
|
|
|
|
if !cmp.Equal(expected, resp.State, typeComparer, valueComparer, equateEmpty) {
|
|
t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty))
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_OpenEphemeralResource(t *testing.T) {
|
|
t.Run("success", func(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
future := time.Now().Add(time.Minute)
|
|
client.EXPECT().OpenEphemeralResource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.OpenEphemeralResource_Response{
|
|
Result: &proto.DynamicValue{
|
|
Msgpack: []byte("\x81\xa4attr\xa3bar"),
|
|
},
|
|
Private: []byte("private data"),
|
|
RenewAt: timestamppb.New(future),
|
|
}, nil)
|
|
|
|
resp := p.OpenEphemeralResource(t.Context(), providers.OpenEphemeralResourceRequest{
|
|
TypeName: "eph",
|
|
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 diff := cmp.Diff(expected, resp.Result, typeComparer, valueComparer, equateEmpty); diff != "" {
|
|
t.Fatalf("expected to have no diff between the expected result and result from the openEphemeral. got: %s", diff)
|
|
}
|
|
if resp.RenewAt == nil || !future.Equal(*resp.RenewAt) {
|
|
t.Fatalf("unexpected renewAt. got: %s, want %s", resp.RenewAt, future)
|
|
}
|
|
if got, want := resp.Private, []byte("private data"); !slices.Equal(got, want) {
|
|
t.Fatalf("unexpected private data. got: %q, want %q", got, want)
|
|
}
|
|
})
|
|
t.Run("requested type is not in schema", func(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
resp := p.OpenEphemeralResource(t.Context(), providers.OpenEphemeralResourceRequest{
|
|
TypeName: "non_existing",
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
"attr": cty.StringVal("foo"),
|
|
}),
|
|
})
|
|
checkDiagsHasError(t, resp.Diagnostics)
|
|
if got, want := resp.Diagnostics.Err().Error(), `unknown ephemeral resource "non_existing"`; !strings.Contains(got, want) {
|
|
t.Fatalf("diagnostis does not contain the expected content. got: %s; want: %s", got, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGRPCProvider_RenewEphemeralResource(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
client := mockproto.NewMockProviderClient(ctrl)
|
|
p := newGRPCProvider(client)
|
|
|
|
future := time.Now().Add(time.Minute)
|
|
client.EXPECT().RenewEphemeralResource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.RenewEphemeralResource_Response{
|
|
Private: []byte("private data new"),
|
|
RenewAt: timestamppb.New(future),
|
|
}, nil)
|
|
|
|
resp := p.RenewEphemeralResource(t.Context(), providers.RenewEphemeralResourceRequest{
|
|
TypeName: "eph",
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
|
|
if resp.RenewAt == nil || !future.Equal(*resp.RenewAt) {
|
|
t.Fatalf("unexpected renewAt. got: %s, want %s", resp.RenewAt, future)
|
|
}
|
|
|
|
if got, want := resp.Private, []byte("private data new"); !slices.Equal(got, want) {
|
|
t.Fatalf("unexpected private data. got: %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestGRPCProvider_CloseEphemeralResource(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
client := mockproto.NewMockProviderClient(ctrl)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().CloseEphemeralResource(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.CloseEphemeralResource_Response{}, nil)
|
|
|
|
resp := p.CloseEphemeralResource(t.Context(), providers.CloseEphemeralResourceRequest{
|
|
TypeName: "eph",
|
|
})
|
|
|
|
checkDiags(t, resp.Diagnostics)
|
|
}
|
|
|
|
func TestGRPCProvider_CallFunction(t *testing.T) {
|
|
client := mockProviderClient(t)
|
|
p := newGRPCProvider(client)
|
|
|
|
client.EXPECT().CallFunction(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&proto.CallFunction_Response{
|
|
Result: &proto.DynamicValue{Json: []byte(`"foo"`)},
|
|
}, nil)
|
|
|
|
resp := p.CallFunction(t.Context(), providers.CallFunctionRequest{
|
|
Name: "fn",
|
|
Arguments: []cty.Value{cty.StringVal("bar"), cty.NilVal},
|
|
})
|
|
|
|
if resp.Error != nil {
|
|
t.Fatal(resp.Error)
|
|
}
|
|
if resp.Result != cty.StringVal("foo") {
|
|
t.Fatalf("%v", resp.Result)
|
|
}
|
|
}
|