1package plugin
2
3import (
4	"context"
5	"reflect"
6	"testing"
7
8	grpctest "github.com/hashicorp/go-plugin/test/grpc"
9	"github.com/jhump/protoreflect/grpcreflect"
10	"google.golang.org/grpc"
11	reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
12)
13
14func TestGRPCClient_App(t *testing.T) {
15	client, server := TestPluginGRPCConn(t, map[string]Plugin{
16		"test": new(testGRPCInterfacePlugin),
17	})
18	defer client.Close()
19	defer server.Stop()
20
21	raw, err := client.Dispense("test")
22	if err != nil {
23		t.Fatalf("err: %s", err)
24	}
25
26	impl, ok := raw.(testInterface)
27	if !ok {
28		t.Fatalf("bad: %#v", raw)
29	}
30
31	result := impl.Double(21)
32	if result != 42 {
33		t.Fatalf("bad: %#v", result)
34	}
35
36	err = impl.Bidirectional()
37	if err != nil {
38		t.Fatal(err)
39	}
40}
41
42func TestGRPCConn_BidirectionalPing(t *testing.T) {
43	conn, _ := TestGRPCConn(t, func(s *grpc.Server) {
44		grpctest.RegisterPingPongServer(s, &pingPongServer{})
45	})
46	defer conn.Close()
47	pingPongClient := grpctest.NewPingPongClient(conn)
48
49	pResp, err := pingPongClient.Ping(context.Background(), &grpctest.PingRequest{})
50	if err != nil {
51		t.Fatal(err)
52	}
53	if pResp.Msg != "pong" {
54		t.Fatal("Bad PingPong")
55	}
56}
57
58func TestGRPCC_Stream(t *testing.T) {
59	client, server := TestPluginGRPCConn(t, map[string]Plugin{
60		"test": new(testGRPCInterfacePlugin),
61	})
62	defer client.Close()
63	defer server.Stop()
64
65	raw, err := client.Dispense("test")
66	if err != nil {
67		t.Fatalf("err: %s", err)
68	}
69
70	impl, ok := raw.(testStreamer)
71	if !ok {
72		t.Fatalf("bad: %#v", raw)
73	}
74
75	expected := []int32{21, 22, 23, 24, 25, 26}
76	result, err := impl.Stream(21, 27)
77	if err != nil {
78		t.Fatal(err)
79	}
80
81	if !reflect.DeepEqual(result, expected) {
82		t.Fatalf("expected: %v\ngot: %v", expected, result)
83	}
84}
85
86func TestGRPCClient_Ping(t *testing.T) {
87	client, server := TestPluginGRPCConn(t, map[string]Plugin{
88		"test": new(testGRPCInterfacePlugin),
89	})
90	defer client.Close()
91	defer server.Stop()
92
93	// Run a couple pings
94	if err := client.Ping(); err != nil {
95		t.Fatalf("err: %s", err)
96	}
97	if err := client.Ping(); err != nil {
98		t.Fatalf("err: %s", err)
99	}
100
101	// Close the remote end
102	server.server.Stop()
103
104	// Test ping fails
105	if err := client.Ping(); err == nil {
106		t.Fatal("should error")
107	}
108}
109
110func TestGRPCClient_Reflection(t *testing.T) {
111	ctx := context.Background()
112
113	client, server := TestPluginGRPCConn(t, map[string]Plugin{
114		"test": new(testGRPCInterfacePlugin),
115	})
116	defer client.Close()
117	defer server.Stop()
118
119	refClient := grpcreflect.NewClient(ctx, reflectpb.NewServerReflectionClient(client.Conn))
120
121	svcs, err := refClient.ListServices()
122	if err != nil {
123		t.Fatalf("err: %s", err)
124	}
125
126	// TODO: maybe only assert some specific services here to make test more resilient
127	expectedSvcs := []string{"grpc.health.v1.Health", "grpc.reflection.v1alpha.ServerReflection", "grpctest.Test", "plugin.GRPCBroker", "plugin.GRPCController", "plugin.GRPCStdio"}
128
129	if !reflect.DeepEqual(svcs, expectedSvcs) {
130		t.Fatalf("expected: %v\ngot: %v", expectedSvcs, svcs)
131	}
132
133	healthDesc, err := refClient.ResolveService("grpc.health.v1.Health")
134	if err != nil {
135		t.Fatalf("err: %s", err)
136	}
137
138	methods := healthDesc.GetMethods()
139	var methodNames []string
140	for _, m := range methods {
141		methodNames = append(methodNames, m.GetName())
142	}
143
144	expectedMethodNames := []string{"Check", "Watch"}
145
146	if !reflect.DeepEqual(methodNames, expectedMethodNames) {
147		t.Fatalf("expected: %v\ngot: %v", expectedMethodNames, methodNames)
148	}
149}
150