blob: 4bd8fba05a643d3f151079ca13e395702d687255 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package rpcapi
import (
"context"
"encoding/json"
"net"
"testing"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackstate"
)
// grpcClientForTesting creates an in-memory-only gRPC server, offers the
// caller a chance to register services with it, and then returns a
// client connected to that fake server, with which the caller can construct
// service-specific client objects.
//
// When finished with the returned client, call the close callback given as
// the second return value or else you will leak some goroutines handling the
// server end of this fake connection.
func grpcClientForTesting(ctx context.Context, t *testing.T, registerServices func(srv *grpc.Server)) (conn grpc.ClientConnInterface, close func()) {
fakeListener := bufconn.Listen(1024 /* buffer size */)
srv := grpc.NewServer(
grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
)
// Caller gets an opportunity to register specific services before
// we actually start "serving".
registerServices(srv)
go func() {
if err := srv.Serve(fakeListener); err != nil {
// We can't actually return an error here, but this should
// not arise with our fake listener anyway so we'll just panic.
panic(err)
}
}()
fakeDialer := func(ctx context.Context, fakeAddr string) (net.Conn, error) {
return fakeListener.DialContext(ctx)
}
realConn, err := grpc.DialContext(
ctx, "testfake",
grpc.WithContextDialer(fakeDialer),
grpc.WithInsecure(),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
)
if err != nil {
t.Fatalf("failed to connect to the fake server: %s", err)
}
return realConn, func() {
realConn.Close()
srv.Stop()
fakeListener.Close()
}
}
func appliedChangeToRawState(t *testing.T, changes []stackstate.AppliedChange) map[string]*anypb.Any {
ret := make(map[string]*anypb.Any)
for _, change := range changes {
raw, err := change.AppliedChangeProto()
if err != nil {
t.Fatalf("failed to marshal change to proto: %s", err)
}
for _, raw := range raw.Raw {
ret[raw.Key] = raw.Value
}
}
return ret
}
func mustDefaultRootProvider(provider string) addrs.AbsProviderConfig {
return addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider(provider),
}
}
func mustAbsComponentInstance(t *testing.T, addr string) stackaddrs.AbsComponentInstance {
ret, diags := stackaddrs.ParseAbsComponentInstanceStr(addr)
if len(diags) > 0 {
t.Fatalf("failed to parse component instance address %q: %s", addr, diags)
}
return ret
}
func mustAbsComponent(t *testing.T, addr string) stackaddrs.AbsComponent {
ret, diags := stackaddrs.ParseAbsComponentInstanceStr(addr)
if len(diags) > 0 {
t.Fatalf("failed to parse component instance address %q: %s", addr, diags)
}
if ret.Item.Key != addrs.NoKey {
t.Fatalf("expected component address %q to have no key, but got %q", addr, ret.Item.Key)
}
return stackaddrs.AbsComponent{
Stack: ret.Stack,
Item: ret.Item.Component,
}
}
func mustAbsResourceInstanceObject(t *testing.T, addr string) stackaddrs.AbsResourceInstanceObject {
ret, diags := stackaddrs.ParseAbsResourceInstanceObjectStr(addr)
if len(diags) > 0 {
t.Fatalf("failed to parse resource instance object address %q: %s", addr, diags)
}
return ret
}
func mustMarshalAnyPb(msg proto.Message) *anypb.Any {
var ret anypb.Any
err := anypb.MarshalFrom(&ret, msg, proto.MarshalOptions{})
if err != nil {
panic(err)
}
return &ret
}
func mustMarshalJSONAttrs(attrs map[string]interface{}) []byte {
jsonAttrs, err := json.Marshal(attrs)
if err != nil {
panic(err)
}
return jsonAttrs
}