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:
committed by
GitHub
parent
428900b58b
commit
c393b81bbc
3
Makefile
3
Makefile
@@ -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
|
||||
|
||||
@@ -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, ¬FoundError) {
|
||||
c.JSON(http.StatusNotFound, common.ErrorResponse(err, http.StatusNotFound, nil))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusInternalServerError, nil))
|
||||
}
|
||||
|
||||
142
cmd/app/handler/team_handler_test.go
Normal file
142
cmd/app/handler/team_handler_test.go
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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)
|
||||
|
||||
29
model/customErrors/customErrors.go
Normal file
29
model/customErrors/customErrors.go
Normal 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}}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -39,4 +39,5 @@ type ISlackService interface {
|
||||
severityEntity *severity.SeverityEntity,
|
||||
incidentEntity *incident.IncidentEntity,
|
||||
) error
|
||||
GetConversationInfo(channelId string) (*slack.Channel, error)
|
||||
}
|
||||
|
||||
155
service/teamService/team_service_v2.go
Normal file
155
service/teamService/team_service_v2.go
Normal 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
|
||||
}
|
||||
10
service/teamService/team_service_v2_interface.go
Normal file
10
service/teamService/team_service_v2_interface.go
Normal 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)
|
||||
}
|
||||
200
service/teamService/team_service_v2_test.go
Normal file
200
service/teamService/team_service_v2_test.go
Normal 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")
|
||||
}
|
||||
Reference in New Issue
Block a user