TP-47335 : Update get teams api to reduce latency by getting user data from database instead of slack (#284)

* TP-47335| created teamservice version 2 for get teams api

* TP-47335| modified the getusers info function to handle nil error

* refactored the structure of team service and created interfaces

* TP-47335| created unit tests

* TP-47335| added unit tests for get teams api

* resolved PR comments

* created custom error types

* made some changes in unit tests

* added unit tests for team handler

* solved merge conflicts

* solved invalid users bug

* resolved merge conflicts

* restricting incident title length to 100 characters

* removed unecessary comments
This commit is contained in:
Gullipalli Chetan Kumar
2023-12-04 15:16:21 +05:30
committed by GitHub
parent 428900b58b
commit c393b81bbc
13 changed files with 624 additions and 29 deletions

View File

@@ -48,5 +48,4 @@ generatemocks:
cd $(CURDIR)/model/user && minimock -i IUserRepository -s _mock.go -o $(CURDIR)/mocks
cd $(CURDIR)/pkg/documentService && minimock -i ServiceActions -s _mock.go -o $(CURDIR)/mocks
cd $(CURDIR)/repository/rcaInput && minimock -i IRcaInputRepository -s _mock.go -o $(CURDIR)/mocks
cd $(CURDIR)/model/user && minimock -i IUserRepositoryInterface -s _mock.go -o $(CURDIR)/mocks
cd $(CURDIR)/pkg/monitoringService && minimock -i MonitoringServiceActions -s _mock.go -o $(CURDIR)/mocks
cd $(CURDIR)/service/teamService && minimock -i ITeamServiceV2 -s _mock.go -o $(CURDIR)/mocks

View File

@@ -1,38 +1,64 @@
package handler
import (
"houston/api/request"
"houston/logger"
"net/http"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
"houston/common/util"
"houston/logger"
"houston/model/customErrors"
common "houston/service/response/common"
"houston/service/teamService"
"net/http"
"strconv"
)
type teamHandler struct {
db *gorm.DB
type TeamHandler struct {
gin *gin.Engine
service teamService.ITeamServiceV2
}
func NewTeamHandler(gin *gin.Engine, db *gorm.DB) *teamHandler {
return &teamHandler{
db: db,
}
func NewTeamHandler(gin *gin.Engine, service teamService.ITeamServiceV2) *TeamHandler {
return &TeamHandler{gin: gin, service: service}
}
func (th *teamHandler) AddTeam(c *gin.Context) {
var addTeamRequest request.AddTeamRequest
if err := c.ShouldBindJSON(&addTeamRequest); err != nil {
c.JSON(http.StatusInternalServerError, err)
func (handler *TeamHandler) HandleGetTeamDetails(c *gin.Context) {
id := c.Param(util.IdParam)
teamId, err := strconv.Atoi(id)
if err != nil || teamId <= 0 {
logger.Error(fmt.Sprintf("invalid team id: %d", teamId))
c.JSON(http.StatusBadRequest, common.ErrorResponse(errors.New(fmt.Sprintf("invalid team id %d", teamId)), http.StatusBadRequest, nil))
return
}
logger.Info("add team request received", zap.String("team_name", addTeamRequest.Name))
//err := query.AddTeam(th.db, addTeamRequest)
//if err != nil {
// c.JSON(http.StatusInternalServerError, err)
// return
//}
c.JSON(http.StatusOK, nil)
teamResponse, err := handler.service.GetTeamDetails(uint(teamId))
if err != nil {
handler.handleErrorResponse(c, err)
return
}
c.JSON(http.StatusOK, common.SuccessResponse(teamResponse, http.StatusOK))
}
func (handler *TeamHandler) HandleGetAllTeams(c *gin.Context) {
teamResponses, err := handler.service.GetAllTeams()
if err != nil {
handler.handleErrorResponse(c, err)
return
}
c.JSON(http.StatusOK, common.SuccessResponse(teamResponses, http.StatusOK))
}
func (handler *TeamHandler) handleErrorResponse(c *gin.Context, err error) {
var dataAccessError *customErrors.DataAccessError
if errors.As(err, &dataAccessError) {
c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusInternalServerError, nil))
return
}
var notFoundError *customErrors.NotFoundError
if errors.As(err, &notFoundError) {
c.JSON(http.StatusNotFound, common.ErrorResponse(err, http.StatusNotFound, nil))
return
}
c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusInternalServerError, nil))
}

View File

@@ -0,0 +1,142 @@
package handler
import (
"github.com/gin-gonic/gin"
"github.com/gojuno/minimock/v3"
"github.com/stretchr/testify/suite"
"houston/logger"
"houston/mocks"
"houston/model/customErrors"
service "houston/service/response"
"net/http"
"net/http/httptest"
"testing"
)
type TeamHandlerSuite struct {
suite.Suite
}
func (suite *TeamHandlerSuite) SetupSuite() {
logger.InitLogger()
}
func TestTeamHandler(t *testing.T) {
suite.Run(t, new(TeamHandlerSuite))
}
func (suite *TeamHandlerSuite) TestHandleGetTeamDetailsInvalidTeamId() {
controller := minimock.NewController(suite.T())
suite.T().Cleanup(controller.Finish)
teamService := mocks.NewITeamServiceV2Mock(controller)
handler := NewTeamHandler(nil, teamService)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{
{Key: "id", Value: "invalid"},
}
handler.HandleGetTeamDetails(c)
suite.Equal(http.StatusBadRequest, w.Code)
c.Params = gin.Params{
{Key: "id", Value: "-1"},
}
handler.HandleGetTeamDetails(c)
suite.Equal(http.StatusBadRequest, w.Code)
}
func (suite *TeamHandlerSuite) TestHandleGetTeamDetailsDataAccessError() {
controller := minimock.NewController(suite.T())
suite.T().Cleanup(controller.Finish)
teamService := mocks.NewITeamServiceV2Mock(controller)
teamService.GetTeamDetailsMock.When(1).Then(nil, customErrors.NewDataAccessError("data access error"))
handler := NewTeamHandler(nil, teamService)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{
{Key: "id", Value: "1"},
}
handler.HandleGetTeamDetails(c)
suite.Equal(http.StatusInternalServerError, w.Code)
}
func (suite *TeamHandlerSuite) TestHandleGetTeamDetailsNotFoundError() {
controller := minimock.NewController(suite.T())
suite.T().Cleanup(controller.Finish)
teamService := mocks.NewITeamServiceV2Mock(controller)
teamService.GetTeamDetailsMock.When(2).Then(nil, customErrors.NewNotFoundError("not found error"))
handler := NewTeamHandler(nil, teamService)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{
{Key: "id", Value: "2"},
}
handler.HandleGetTeamDetails(c)
suite.Equal(http.StatusNotFound, w.Code)
}
func (suite *TeamHandlerSuite) TestHandleGetTeamDetailsSuccess() {
controller := minimock.NewController(suite.T())
suite.T().Cleanup(controller.Finish)
teamService := mocks.NewITeamServiceV2Mock(controller)
expectedResponse := &service.TeamResponse{
ID: 1,
Name: "team1",
}
teamService.GetTeamDetailsMock.When(1).Then(expectedResponse, nil)
handler := NewTeamHandler(nil, teamService)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{
{Key: "id", Value: "1"},
}
handler.HandleGetTeamDetails(c)
suite.Equal(http.StatusOK, w.Code)
}
func (suite *TeamHandlerSuite) TestHandleGetAllTeamsFailure() {
controller := minimock.NewController(suite.T())
suite.T().Cleanup(controller.Finish)
teamService := mocks.NewITeamServiceV2Mock(controller)
teamService.GetAllTeamsMock.Expect().Return(nil, customErrors.NewDataAccessError("data access error"))
handler := NewTeamHandler(nil, teamService)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
handler.HandleGetAllTeams(c)
suite.Equal(http.StatusInternalServerError, w.Code)
}
func (suite *TeamHandlerSuite) TestHandleGetAllTeamsSuccess() {
controller := minimock.NewController(suite.T())
suite.T().Cleanup(controller.Finish)
teamService := mocks.NewITeamServiceV2Mock(controller)
expectedResponse := []service.TeamResponse{
{
ID: 1,
Name: "team1",
},
}
teamService.GetAllTeamsMock.Expect().Return(expectedResponse, nil)
handler := NewTeamHandler(nil, teamService)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
handler.HandleGetAllTeams(c)
suite.Equal(http.StatusOK, w.Code)
}

View File

@@ -11,6 +11,8 @@ import (
"houston/internal/metrics"
"houston/logger"
"houston/model/ingester"
"houston/model/log"
"houston/model/team"
"houston/model/user"
"houston/pkg/rest"
"houston/pkg/slackbot"
@@ -21,6 +23,7 @@ import (
"houston/service/incident"
"houston/service/rca"
"houston/service/slack"
"houston/service/teamService"
"net/http"
"strings"
"time"
@@ -77,9 +80,20 @@ func (s *Server) teamHandler(houstonGroup *gin.RouterGroup) {
s.gin.GET("/teams/:id", teamHandler.GetTeams)
s.gin.POST("/teams", teamHandler.UpdateTeam)
if viper.GetBool("get.teams.v2.enabled") {
logRepository := log.NewLogRepository(s.db)
teamRepository := team.NewTeamRepository(s.db, logRepository)
userRepository := user.NewUserRepository(s.db)
slackService := slack.NewSlackService()
teamServiceV2 := teamService.NewTeamServiceV2(teamRepository, userRepository, slackService)
teamHandlerV2 := handler.NewTeamHandler(s.gin, teamServiceV2)
houstonGroup.GET("/teams", teamHandlerV2.HandleGetAllTeams)
houstonGroup.GET("/teams/:id", teamHandlerV2.HandleGetTeamDetails)
} else {
houstonGroup.GET("/teams", teamHandler.GetTeams)
houstonGroup.GET("/teams/:id", teamHandler.GetTeams)
}
houstonGroup.POST("/teams/add", teamHandler.AddTeam)
houstonGroup.GET("/teams", teamHandler.GetTeams)
houstonGroup.GET("/teams/:id", teamHandler.GetTeams)
houstonGroup.POST("/teams", teamHandler.UpdateTeam)
houstonGroup.PATCH("/teams/:id/manager/:userId", teamHandler.MakeManager)
houstonGroup.DELETE("/teams/:id/members/:userId", teamHandler.RemoveTeamMember)

View File

@@ -45,6 +45,7 @@ const (
DeactivatedUserName = "Deactivated User"
AlreadyArchivedError = "already_archived"
NotInChannelError = "not_in_channel"
NotFoundError = "not found"
)
const (
@@ -91,3 +92,6 @@ const (
const (
DocumentServiceProvider = "SA_DOCUMENT_SERVICE"
)
const (
IdParam = "id"
)

View File

@@ -83,6 +83,7 @@ create-incident.description.max-length=500
create-incident.title.max-length=100
create-incident-v2-enabled=CREATE_INCIDENT_V2_ENABLED
get-teams.v2.enabled=GET_TEAMS_V2_ENABLED
#slack details
slack.workspace.id=SLACK_WORKSPACE_ID
navi.jira.base.url=https://navihq.atlassian.net/

View File

@@ -9,6 +9,8 @@ import (
"github.com/slack-go/slack"
)
const IncidentTitleLength = 100
func GenerateModalRequest(teams []team.TeamEntity, severities []severity.SeverityEntity, channel string) slack.ModalViewRequest {
// Create a ModalViewRequest with a header and two inputs
titleText := slack.NewTextBlockObject(slack.PlainTextType, "Houston", false, false)
@@ -39,6 +41,7 @@ func GenerateModalRequest(teams []team.TeamEntity, severities []severity.Severit
incidentTitleText := slack.NewTextBlockObject(slack.PlainTextType, "Incident title", false, false)
incidentTitlePlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Write something", false, false)
incidentTitleElement := slack.NewPlainTextInputBlockElement(incidentTitlePlaceholder, "title")
incidentTitleElement.MaxLength = IncidentTitleLength
incidentTitle := slack.NewInputBlock("Incident title", incidentTitleText, nil, incidentTitleElement)
incidentDescriptionText := slack.NewTextBlockObject(slack.PlainTextType, "Incident description", false, false)

View File

@@ -0,0 +1,29 @@
package customErrors
type CustomError struct {
Message string
}
func NewCustomError(message string) error {
return &CustomError{Message: message}
}
func (e *CustomError) Error() string {
return e.Message
}
type DataAccessError struct {
CustomError
}
func NewDataAccessError(message string) error {
return &DataAccessError{CustomError{Message: message}}
}
type NotFoundError struct {
CustomError
}
func NewNotFoundError(message string) error {
return &NotFoundError{CustomError{Message: message}}
}

View File

@@ -477,3 +477,14 @@ func convertToConversationResponse(message slack.Message) service.ConversationRe
TimeStamp: message.Timestamp,
}
}
func (s *SlackService) GetConversationInfo(channelId string) (*slack.Channel, error) {
channel, err := s.SocketModeClient.GetConversationInfo(&slack.GetConversationInfoInput{
ChannelID: channelId,
})
if err != nil {
logger.Info("failed while fetching conversation info", zap.Error(err))
return nil, err
}
return channel, nil
}

View File

@@ -39,4 +39,5 @@ type ISlackService interface {
severityEntity *severity.SeverityEntity,
incidentEntity *incident.IncidentEntity,
) error
GetConversationInfo(channelId string) (*slack.Channel, error)
}

View File

@@ -0,0 +1,155 @@
package teamService
import (
"errors"
"fmt"
"go.uber.org/zap"
"houston/common/util"
"houston/logger"
"houston/model/customErrors"
"houston/model/team"
"houston/model/user"
service "houston/service/response"
"houston/service/slack"
)
type TeamServiceV2 struct {
teamRepository team.ITeamRepository
userRepository user.IUserRepository
slackService slack.ISlackService
}
func NewTeamServiceV2(teamRepository team.ITeamRepository,
userRepository user.IUserRepository,
slackService slack.ISlackService) *TeamServiceV2 {
return &TeamServiceV2{
teamRepository: teamRepository,
userRepository: userRepository,
slackService: slackService,
}
}
const logTag = "[team_service_v2]"
func (teamService *TeamServiceV2) GetTeamDetails(teamId uint) (*service.TeamResponse, error) {
currentTeam, err := teamService.teamRepository.FindTeamById(teamId)
if err != nil {
logger.Error(fmt.Sprintf("%s error in fetching team by teamId: %d", logTag, teamId), zap.Error(err))
return nil, customErrors.NewDataAccessError(fmt.Sprintf("error in fetching team with id: %d", teamId))
}
if currentTeam == nil {
logger.Error(fmt.Sprintf("%s team not found for teamId: %d", logTag, teamId))
return nil, customErrors.NewNotFoundError(fmt.Sprintf("team not found for teamId: %d", teamId))
}
if !currentTeam.Active {
logger.Error(fmt.Sprintf("%s team is inactive for teamId: %d", logTag, teamId))
return nil, customErrors.NewNotFoundError(fmt.Sprintf("team is inactive for teamId: %d", teamId))
}
teamResponse := service.ConvertToTeamResponse(*currentTeam)
users, err := teamService.userRepository.GetHoustonUsersBySlackId(currentTeam.SlackUserIds)
if err != nil {
logger.Error(fmt.Sprintf("%s error in fetching users by slackIds: %s", logTag, currentTeam.SlackUserIds))
return nil, customErrors.NewDataAccessError(fmt.Sprintf("unable to find users for team id: %d", teamId))
}
teamResponse.Participants = teamService.getTeamUserResponses(*users, currentTeam.SlackUserIds)
teamResponse.WebhookSlackChannelId = currentTeam.WebhookSlackChannel
channel, err := teamService.slackService.GetConversationInfo(currentTeam.WebhookSlackChannel)
if err != nil {
logger.Error(fmt.Sprintf("%s error in fetching channel info for channel: %s", logTag, currentTeam.WebhookSlackChannel))
teamResponse.WebhookSlackChannelName = util.NotFoundError
} else {
teamResponse.WebhookSlackChannelName = channel.Name
}
onCallBot, botErr := teamService.getTeamBotDetails(currentTeam.OncallHandle)
if botErr != nil {
logger.Error(fmt.Sprintf("%s error in fetching oncall bot info for user id: %s", logTag, currentTeam.OncallHandle), zap.Error(botErr))
} else {
teamResponse.Oncall = *onCallBot
}
pseOnCallBot, botErr := teamService.getTeamBotDetails(currentTeam.PseOncallHandle)
if botErr != nil {
logger.Error(fmt.Sprintf("%s error in fetching pse oncall bot info for user id: %s", logTag, currentTeam.PseOncallHandle), zap.Error(botErr))
} else {
teamResponse.PseOncall = *pseOnCallBot
}
return &teamResponse, nil
}
func (teamService *TeamServiceV2) GetAllTeams() ([]service.TeamResponse, error) {
var teamResponses []service.TeamResponse
teams, err := teamService.teamRepository.GetAllActiveTeams()
if err != nil {
logger.Error(fmt.Sprintf("%s error in fetching all teams", logTag))
return nil, customErrors.NewDataAccessError(fmt.Sprintf("error in fetching all teams"))
}
if teams == nil {
logger.Error(fmt.Sprintf("%s no teams found", logTag))
return nil, customErrors.NewNotFoundError(fmt.Sprintf("no teams found"))
}
for _, currentTeam := range *teams {
teamResponses = append(teamResponses, service.ConvertToTeamResponse(currentTeam))
}
return teamResponses, nil
}
func (teamService *TeamServiceV2) getTeamBotDetails(slackUserId string) (*service.BotResponse, error) {
bot, err := teamService.userRepository.FindHoustonUserBySlackUserId(slackUserId)
if err != nil {
return nil, err
}
if bot == nil {
return nil, errors.New(fmt.Sprintf("bot with slack user id: %s is not found", slackUserId))
}
return &service.BotResponse{
Id: bot.SlackUserId,
Name: bot.RealName,
}, nil
}
func (teamService *TeamServiceV2) getTeamUserResponses(users []user.UserEntity, slackUserIds []string) []service.UserResponse {
existingSlackUserIdsMap := make(map[string]string)
var userResponses []service.UserResponse
for _, currentUser := range users {
userResponses = append(userResponses, service.UserResponse{
Id: currentUser.SlackUserId,
Name: currentUser.RealName,
Email: currentUser.Email,
Image: currentUser.Image,
})
existingSlackUserIdsMap[currentUser.SlackUserId] = currentUser.SlackUserId
}
if len(slackUserIds) == len(existingSlackUserIdsMap) {
return userResponses
}
//If users data not found in db fetch from slack
var nonExistingSlackUserIds []string
for _, slackUserId := range slackUserIds {
if _, ok := existingSlackUserIdsMap[slackUserId]; !ok {
nonExistingSlackUserIds = append(nonExistingSlackUserIds, slackUserId)
}
}
extraUsers := teamService.slackService.GetUsersInfo(nonExistingSlackUserIds...)
if extraUsers == nil || len(*extraUsers) == 0 {
for _, slackUserId := range nonExistingSlackUserIds {
userResponses = append(userResponses, service.UserResponse{
Id: slackUserId,
})
}
} else {
for _, currentUser := range *extraUsers {
userResponses = append(userResponses, service.UserResponse{
Id: currentUser.ID,
Name: currentUser.Profile.RealName,
Email: currentUser.Profile.Email,
Image: currentUser.Profile.Image32,
})
}
}
return userResponses
}

View File

@@ -0,0 +1,10 @@
package teamService
import (
service "houston/service/response"
)
type ITeamServiceV2 interface {
GetTeamDetails(teamId uint) (*service.TeamResponse, error)
GetAllTeams() ([]service.TeamResponse, error)
}

View File

@@ -0,0 +1,200 @@
package teamService
import (
"errors"
"github.com/gojuno/minimock/v3"
"github.com/slack-go/slack"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"gorm.io/gorm"
"houston/logger"
"houston/mocks"
"houston/model/team"
"houston/model/user"
service "houston/service/response"
"testing"
)
type TeamServiceV2Suite struct {
suite.Suite
controller *minimock.Controller
teamRepository *mocks.ITeamRepositoryMock
userRepository *mocks.IUserRepositoryMock
slackService *mocks.ISlackServiceMock
teamService *TeamServiceV2
}
func (suite *TeamServiceV2Suite) SetupSuite() {
logger.InitLogger()
suite.controller = minimock.NewController(suite.T())
suite.T().Cleanup(suite.controller.Finish)
suite.teamRepository = mocks.NewITeamRepositoryMock(suite.controller)
suite.userRepository = mocks.NewIUserRepositoryMock(suite.controller)
suite.slackService = mocks.NewISlackServiceMock(suite.controller)
suite.teamService = NewTeamServiceV2(suite.teamRepository, suite.userRepository, suite.slackService)
}
func TestTeamServiceV2(t *testing.T) {
suite.Run(t, new(TeamServiceV2Suite))
}
func (suite *TeamServiceV2Suite) TestGetTeamDetails_Failure() {
suite.teamRepository.FindTeamByIdMock.When(1).Then(nil, errors.New("error"))
suite.teamRepository.FindTeamByIdMock.When(20).Then(nil, nil)
suite.teamRepository.FindTeamByIdMock.When(10).Then(&team.TeamEntity{Active: false}, nil)
_, err := suite.teamService.GetTeamDetails(1)
assert.EqualError(suite.T(), err, "error in fetching team with id: 1")
teamResponse, err := suite.teamService.GetTeamDetails(20)
assert.Nil(suite.T(), teamResponse)
assert.EqualError(suite.T(), err, "team not found for teamId: 20")
teamResponse, err = suite.teamService.GetTeamDetails(10)
assert.Nil(suite.T(), teamResponse)
assert.EqualError(suite.T(), err, "team is inactive for teamId: 10")
}
func (suite *TeamServiceV2Suite) TestGetTeamDetails_Success() {
suite.teamRepository.FindTeamByIdMock.When(2).Then(&team.TeamEntity{
Model: gorm.Model{ID: 2},
Name: "Test Team",
Active: true,
WebhookSlackChannel: "test-channel",
OncallHandle: "oncall-bot",
PseOncallHandle: "pse-oncall-bot",
SlackUserIds: []string{"user1", "user2"},
}, nil)
suite.userRepository.GetHoustonUsersBySlackIdMock.When([]string{"user1", "user2"}).Then(
&[]user.UserEntity{
{SlackUserId: "user1", RealName: "User One", Email: "user.one@example.com", Image: "image1"},
}, nil)
suite.slackService.GetConversationInfoMock.When("test-channel").Then(
&slack.Channel{
GroupConversation: slack.GroupConversation{
Name: "Test Channel",
},
}, nil)
suite.userRepository.FindHoustonUserBySlackUserIdMock.When("oncall-bot").Then(&user.UserEntity{
SlackUserId: "oncall-bot",
RealName: "Oncall Bot",
}, nil)
suite.userRepository.FindHoustonUserBySlackUserIdMock.When("pse-oncall-bot").Then(&user.UserEntity{
SlackUserId: "pse-oncall-bot",
RealName: "PSE Oncall Bot",
}, nil)
suite.slackService.GetUsersInfoMock.When([]string{"user2"}...).Then(&[]slack.User{
{ID: "user2", RealName: "User Two", Profile: slack.UserProfile{Email: "", Image32: "image2", RealName: "User Two"}},
})
teamResponse, err := suite.teamService.GetTeamDetails(2)
assert.Nil(suite.T(), err)
assert.NotNil(suite.T(), teamResponse)
assert.Equal(suite.T(), uint(2), teamResponse.ID)
assert.Equal(suite.T(), "Test Team", teamResponse.Name)
assert.Equal(suite.T(), "Test Channel", teamResponse.WebhookSlackChannelName)
assert.Equal(suite.T(), "Oncall Bot", teamResponse.Oncall.Name)
assert.Equal(suite.T(), "PSE Oncall Bot", teamResponse.PseOncall.Name)
assert.Equal(suite.T(), 2, len(teamResponse.Participants))
}
func (suite *TeamServiceV2Suite) TestGetTeamUserResponses() {
users := []user.UserEntity{
{SlackUserId: "user1", RealName: "User One", Email: "", Image: "image1"},
{SlackUserId: "user2", RealName: "User Two", Email: "", Image: "image2"},
{SlackUserId: "user3", RealName: "User Three", Email: "", Image: "image3"},
{SlackUserId: "user4", RealName: "User Four", Email: "", Image: "image4"},
{SlackUserId: "user5", RealName: "User Five", Email: "", Image: "image5"},
}
expectedUserResponses := []service.UserResponse{
{Id: "user1", Name: "User One", Email: "", Image: "image1"},
{Id: "user2", Name: "User Two", Email: "", Image: "image2"},
{Id: "user3", Name: "User Three", Email: "", Image: "image3"},
{Id: "user4", Name: "User Four", Email: "", Image: "image4"},
{Id: "user5", Name: "User Five", Email: "", Image: "image5"},
}
slackUserIds := []string{"user1", "user2", "user3", "user4", "user5"}
suite.slackService.GetUsersInfoMock.When(slackUserIds[3:5]...).Then(&[]slack.User{
{ID: "user4", Profile: slack.UserProfile{Email: "", Image32: "image4", RealName: "User Four"}},
{ID: "user5", Profile: slack.UserProfile{Email: "", Image32: "image5", RealName: "User Five"}},
})
userResponses := suite.teamService.getTeamUserResponses(users[:3], slackUserIds)
assert.Equal(suite.T(), 5, len(userResponses))
assert.Equal(suite.T(), expectedUserResponses, userResponses)
suite.slackService.GetUsersInfoMock.When(slackUserIds[4]).Then(nil)
userResponses = suite.teamService.getTeamUserResponses(users[:4], slackUserIds)
assert.Equal(suite.T(), 5, len(userResponses))
assert.Equal(suite.T(), expectedUserResponses[:4], userResponses[:4])
}
func (suite *TeamServiceV2Suite) TestGetTeamDetails_NoParticipants() {
// Test scenario where there are no participants for the team
suite.teamRepository.FindTeamByIdMock.When(3).Then(&team.TeamEntity{
Model: gorm.Model{ID: 3},
Name: "Empty Team",
Active: true,
WebhookSlackChannel: "empty-channel",
OncallHandle: "unknown-bot",
PseOncallHandle: "another-unknown-bot",
SlackUserIds: []string{},
}, nil)
suite.slackService.GetConversationInfoMock.When("empty-channel").Then(&slack.Channel{
GroupConversation: slack.GroupConversation{
Name: "Empty Channel",
},
}, nil)
suite.userRepository.FindHoustonUserBySlackUserIdMock.When("unknown-bot").Then(nil, nil)
suite.userRepository.FindHoustonUserBySlackUserIdMock.When("another-unknown-bot").Then(nil, nil)
suite.userRepository.GetHoustonUsersBySlackIdMock.When([]string{}).Then(&[]user.UserEntity{}, nil)
teamResponse, err := suite.teamService.GetTeamDetails(3)
assert.Nil(suite.T(), err)
assert.NotNil(suite.T(), teamResponse)
assert.Nil(suite.T(), teamResponse.Participants)
assert.Empty(suite.T(), teamResponse.Oncall)
assert.Empty(suite.T(), teamResponse.PseOncall)
}
func (suite *TeamServiceV2Suite) TestGetTeamUserResponses_SlackServiceFailure() {
slackUserIds := []string{"user1", "user2", "user3", "user4", "user5"}
suite.slackService.GetUsersInfoMock.When(slackUserIds...).Then(nil)
userResponses := suite.teamService.getTeamUserResponses([]user.UserEntity{}, slackUserIds)
assert.Equal(suite.T(), 5, len(userResponses))
for _, userResponse := range userResponses {
assert.Contains(suite.T(), slackUserIds, userResponse.Id)
assert.Empty(suite.T(), userResponse.Name)
assert.Empty(suite.T(), userResponse.Email)
assert.Empty(suite.T(), userResponse.Image)
}
}
func (suite *TeamServiceV2Suite) TestGetAllTeams_NoTeamsFailure() {
suite.teamRepository.GetAllActiveTeamsMock.Expect().Return(nil, nil)
teamResponses, err := suite.teamService.GetAllTeams()
assert.Nil(suite.T(), teamResponses)
assert.EqualError(suite.T(), err, "no teams found")
}
func (suite *TeamServiceV2Suite) TestGetAllTeams_Failure() {
suite.teamRepository.GetAllActiveTeamsMock.Expect().Return(nil, errors.New("error"))
teamResponses, err := suite.teamService.GetAllTeams()
assert.Nil(suite.T(), teamResponses)
assert.EqualError(suite.T(), err, "error in fetching all teams")
}