diff --git a/tools/goctl/rpc/generator/gencall_test.go b/tools/goctl/rpc/generator/gencall_test.go new file mode 100644 index 000000000..deb773a28 --- /dev/null +++ b/tools/goctl/rpc/generator/gencall_test.go @@ -0,0 +1,125 @@ +package generator + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/emicklei/proto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + conf "github.com/zeromicro/go-zero/tools/goctl/config" + "github.com/zeromicro/go-zero/tools/goctl/rpc/parser" + "github.com/zeromicro/go-zero/tools/goctl/util/stringx" +) + +// mockDirContext is a minimal DirContext for unit-testing genCallGroup. +type mockDirContext struct { + callDir Dir + pbDir Dir + protoGo Dir +} + +func (m *mockDirContext) GetCall() Dir { return m.callDir } +func (m *mockDirContext) GetEtc() Dir { return Dir{} } +func (m *mockDirContext) GetInternal() Dir { return Dir{} } +func (m *mockDirContext) GetConfig() Dir { return Dir{} } +func (m *mockDirContext) GetLogic() Dir { return Dir{} } +func (m *mockDirContext) GetServer() Dir { return Dir{} } +func (m *mockDirContext) GetSvc() Dir { return Dir{} } +func (m *mockDirContext) GetPb() Dir { return m.pbDir } +func (m *mockDirContext) GetProtoGo() Dir { return m.protoGo } +func (m *mockDirContext) GetMain() Dir { return Dir{} } +func (m *mockDirContext) GetServiceName() stringx.String { return stringx.From("test") } +func (m *mockDirContext) SetPbDir(pbDir, grpcDir string) {} + +// TestGenCallGroup_OnlyUsedTypesAliased verifies that in multi-service mode each +// generated client file contains type aliases only for the message types actually +// used by that service's RPCs (fix for issue #5481). +func TestGenCallGroup_OnlyUsedTypesAliased(t *testing.T) { + tmpDir := t.TempDir() + callBase := filepath.Join(tmpDir, "call") + pbBase := filepath.Join(tmpDir, "pb") + + // Pre-create subdirs that genCallGroup will write into. + require.NoError(t, os.MkdirAll(filepath.Join(callBase, "servicea"), 0755)) + require.NoError(t, os.MkdirAll(filepath.Join(callBase, "serviceb"), 0755)) + require.NoError(t, os.MkdirAll(pbBase, 0755)) + + mctx := &mockDirContext{ + callDir: Dir{ + Filename: callBase, + Package: "example.com/multitest/call", + Base: "call", + GetChildPackage: func(childPath string) (string, error) { + // Return a package path whose Base() is the lowercase service name. + return filepath.Join(callBase, strings.ToLower(childPath)), nil + }, + }, + pbDir: Dir{ + Filename: pbBase, + Package: "example.com/multitest/pb", + Base: "pb", + }, + protoGo: Dir{ + // Must differ from "servicea"/"serviceb" so isCallPkgSameToPbPkg stays false + // and alias generation is triggered. + Filename: pbBase, + Package: "example.com/multitest/pb", + Base: "pb", + }, + } + + // Proto with two services that use completely disjoint message types. + protoData := parser.Proto{ + Name: "multi.proto", + PbPackage: "pb", + Message: []parser.Message{ + {Message: &proto.Message{Name: "AReq"}}, + {Message: &proto.Message{Name: "AResp"}}, + {Message: &proto.Message{Name: "BReq"}}, + {Message: &proto.Message{Name: "BResp"}}, + }, + Service: parser.Services{ + { + Service: &proto.Service{Name: "ServiceA"}, + RPC: []*parser.RPC{ + {RPC: &proto.RPC{Name: "DoA", RequestType: "AReq", ReturnsType: "AResp"}}, + }, + }, + { + Service: &proto.Service{Name: "ServiceB"}, + RPC: []*parser.RPC{ + {RPC: &proto.RPC{Name: "DoB", RequestType: "BReq", ReturnsType: "BResp"}}, + }, + }, + }, + } + + cfg, err := conf.NewConfig("") + require.NoError(t, err) + + g := NewGenerator("gozero", false) + require.NoError(t, g.genCallGroup(mctx, protoData, cfg)) + + // servicea/servicea.go — aliases for AReq/AResp only + aContent, err := os.ReadFile(filepath.Join(callBase, "servicea", "servicea.go")) + require.NoError(t, err) + aFile := string(aContent) + + assert.Contains(t, aFile, "AReq = pb.AReq", "ServiceA file should alias AReq") + assert.Contains(t, aFile, "AResp = pb.AResp", "ServiceA file should alias AResp") + assert.NotContains(t, aFile, "BReq = pb.BReq", "ServiceA file must not alias BReq") + assert.NotContains(t, aFile, "BResp = pb.BResp", "ServiceA file must not alias BResp") + + // serviceb/serviceb.go — aliases for BReq/BResp only + bContent, err := os.ReadFile(filepath.Join(callBase, "serviceb", "serviceb.go")) + require.NoError(t, err) + bFile := string(bContent) + + assert.Contains(t, bFile, "BReq = pb.BReq", "ServiceB file should alias BReq") + assert.Contains(t, bFile, "BResp = pb.BResp", "ServiceB file should alias BResp") + assert.NotContains(t, bFile, "AReq = pb.AReq", "ServiceB file must not alias AReq") + assert.NotContains(t, bFile, "AResp = pb.AResp", "ServiceB file must not alias AResp") +}