// Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 package grpc_test import ( "context" "fmt" "net" "testing" "time" grpcChannelsV1 "github.com/absmach/magistrala/api/grpc/channels/v1" grpcCommonV1 "github.com/absmach/magistrala/api/grpc/common/v1" apiutil "github.com/absmach/magistrala/api/http/util" "github.com/absmach/magistrala/channels" ch "github.com/absmach/magistrala/channels" grpcapi "github.com/absmach/magistrala/channels/api/grpc" "github.com/absmach/magistrala/channels/private/mocks" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/connections" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/policies" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" ) const port = 7005 var ( validID = testsutil.GenerateUUID(&testing.T{}) validChannel = ch.Channel{ ID: validID, Domain: testsutil.GenerateUUID(&testing.T{}), Status: channels.EnabledStatus, } ) func startGRPCServer(svc *mocks.Service, port int) *grpc.Server { listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { panic(fmt.Sprintf("failed to obtain port: %s", err)) } server := grpc.NewServer() grpcChannelsV1.RegisterChannelsServiceServer(server, grpcapi.NewServer(svc)) go func() { if err := server.Serve(listener); err != nil { panic(fmt.Sprintf("failed to serve: %s", err)) } }() return server } func TestAuthorize(t *testing.T) { svc := new(mocks.Service) server := startGRPCServer(svc, port) defer server.GracefulStop() authAddr := fmt.Sprintf("localhost:%d", port) conn, _ := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) client := grpcapi.NewClient(conn, time.Second) cases := []struct { desc string domainID string clientID string clientType string channelID string connType connections.ConnType err error authzErr error res *grpcChannelsV1.AuthzRes code codes.Code }{ { desc: "authorize successfully", domainID: validID, clientID: validID, clientType: policies.UserType, channelID: validID, connType: connections.Publish, res: &grpcChannelsV1.AuthzRes{Authorized: true}, err: nil, }, { desc: "authorize with authorization error", domainID: validID, clientID: validID, clientType: policies.UserType, channelID: validID, connType: connections.Publish, res: &grpcChannelsV1.AuthzRes{Authorized: false}, authzErr: svcerr.ErrAuthorization, err: svcerr.ErrAuthorization, }, { desc: "authorize withnot found error", domainID: validID, clientID: validID, clientType: policies.UserType, channelID: validID, connType: connections.Publish, res: &grpcChannelsV1.AuthzRes{Authorized: false}, authzErr: svcerr.ErrNotFound, err: svcerr.ErrNotFound, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { authReq := ch.AuthzReq{ DomainID: tc.domainID, ClientID: tc.clientID, ClientType: tc.clientType, ChannelID: tc.channelID, Type: tc.connType, } svcCall := svc.On("Authorize", mock.Anything, authReq).Return(tc.authzErr) res, err := client.Authorize(context.Background(), &grpcChannelsV1.AuthzReq{ DomainId: tc.domainID, ClientId: tc.clientID, ClientType: tc.clientType, ChannelId: tc.channelID, Type: uint32(tc.connType), }) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.res, res, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.res, res)) svcCall.Unset() }) } } func TestRemoveClientConnections(t *testing.T) { svc := new(mocks.Service) server := startGRPCServer(svc, port) defer server.GracefulStop() authAddr := fmt.Sprintf("localhost:%d", port) conn, _ := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) client := grpcapi.NewClient(conn, time.Second) cases := []struct { desc string clientID string err error code codes.Code }{ { desc: "remove client connections successfully", clientID: validID, err: nil, }, { desc: "remove client connections with error", clientID: validID, err: svcerr.ErrNotFound, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { svcCall := svc.On("RemoveClientConnections", mock.Anything, tc.clientID).Return(tc.err) res, err := client.RemoveClientConnections(context.Background(), &grpcChannelsV1.RemoveClientConnectionsReq{ ClientId: tc.clientID, }) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) assert.Equal(t, &grpcChannelsV1.RemoveClientConnectionsRes{}, res) svcCall.Unset() }) } } func TestUnsetParentGroupFromChannelsEndpoint(t *testing.T) { svc := new(mocks.Service) server := startGRPCServer(svc, port) defer server.GracefulStop() authAddr := fmt.Sprintf("localhost:%d", port) conn, _ := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) client := grpcapi.NewClient(conn, time.Second) cases := []struct { desc string parentGroupID string err error code codes.Code }{ { desc: "unset parent group from channels successfully", parentGroupID: validID, err: nil, }, { desc: "unset parent group from channels authorization error", parentGroupID: validID, err: svcerr.ErrAuthorization, }, { desc: "unset parent group from channels with not found error", parentGroupID: validID, err: svcerr.ErrNotFound, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { svcCall := svc.On("UnsetParentGroupFromChannels", mock.Anything, tc.parentGroupID).Return(tc.err) res, err := client.UnsetParentGroupFromChannels(context.Background(), &grpcChannelsV1.UnsetParentGroupFromChannelsReq{ ParentGroupId: tc.parentGroupID, }) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) assert.Equal(t, &grpcChannelsV1.UnsetParentGroupFromChannelsRes{}, res) svcCall.Unset() }) } } func TestRetrieveEntity(t *testing.T) { svc := new(mocks.Service) server := startGRPCServer(svc, port) defer server.GracefulStop() authAddr := fmt.Sprintf("localhost:%d", port) conn, _ := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) client := grpcapi.NewClient(conn, time.Second) cases := []struct { desc string id string svcRes ch.Channel resp *grpcCommonV1.RetrieveEntityRes code codes.Code err error }{ { desc: "retrieve entity successfully", id: validID, svcRes: validChannel, resp: &grpcCommonV1.RetrieveEntityRes{ Entity: &grpcCommonV1.EntityBasic{ Id: validChannel.ID, DomainId: validChannel.Domain, ParentGroupId: validChannel.ParentGroup, Status: uint32(validChannel.Status), }, }, err: nil, }, { desc: "retrieve entity with error", id: validID, resp: &grpcCommonV1.RetrieveEntityRes{}, err: svcerr.ErrNotFound, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { svcCall := svc.On("RetrieveByID", mock.Anything, tc.id).Return(tc.svcRes, tc.err) res, err := client.RetrieveEntity(context.Background(), &grpcCommonV1.RetrieveEntityReq{ Id: tc.id, }) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.resp.Entity, res.Entity) svcCall.Unset() }) } } func TestRetrieveIDByRoute(t *testing.T) { svc := new(mocks.Service) server := startGRPCServer(svc, port) defer server.GracefulStop() authAddr := fmt.Sprintf("localhost:%d", port) conn, _ := grpc.NewClient(authAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) client := grpcapi.NewClient(conn, time.Second) validRoute := "validRoute" domainID := testsutil.GenerateUUID(t) cases := []struct { desc string retrieveReq *grpcCommonV1.RetrieveIDByRouteReq svcRes string svcErr error retrieveRes *grpcCommonV1.RetrieveEntityRes err error }{ { desc: "retrieve entity by route successfully", retrieveReq: &grpcCommonV1.RetrieveIDByRouteReq{ Route: validRoute, DomainId: domainID, }, svcRes: validID, retrieveRes: &grpcCommonV1.RetrieveEntityRes{ Entity: &grpcCommonV1.EntityBasic{ Id: validID, }, }, err: nil, }, { desc: "retrieve entity by route with empty route", retrieveReq: &grpcCommonV1.RetrieveIDByRouteReq{ Route: "", DomainId: domainID, }, svcRes: "", retrieveRes: &grpcCommonV1.RetrieveEntityRes{}, err: apiutil.ErrMissingRoute, }, { desc: "retrieve entity by route with empty domain ID", retrieveReq: &grpcCommonV1.RetrieveIDByRouteReq{ Route: validRoute, DomainId: "", }, svcRes: "", retrieveRes: &grpcCommonV1.RetrieveEntityRes{}, err: apiutil.ErrMissingDomainID, }, { desc: "retrieve entity by route with invalid route", retrieveReq: &grpcCommonV1.RetrieveIDByRouteReq{ Route: "invalidRoute", DomainId: domainID, }, svcRes: "", svcErr: svcerr.ErrNotFound, retrieveRes: &grpcCommonV1.RetrieveEntityRes{}, err: svcerr.ErrNotFound, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { svcCall := svc.On("RetrieveIDByRoute", mock.Anything, tc.retrieveReq.Route, tc.retrieveReq.DomainId).Return(tc.svcRes, tc.svcErr) res, err := client.RetrieveIDByRoute(context.Background(), tc.retrieveReq) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.retrieveRes.Entity, res.Entity) svcCall.Unset() }) } }