INFRA-3811 : Tag Value Management Module (#458)

* INFRA-3811 : Tag Value Management Module

* INFRA-3811 : PR review comments

* INFRA-3811 : Entity/Association update fix
This commit is contained in:
Vijay Joshi
2024-09-25 15:23:32 +05:30
committed by GitHub
parent 4dcffd1e72
commit 5332db8ed3
15 changed files with 630 additions and 13 deletions

View File

@@ -0,0 +1,6 @@
package tagValue
type AddTagValueRequest struct {
TagId *uint `json:"tag_id"`
Value *string `json:"value"`
}

View File

@@ -0,0 +1,8 @@
package tagValue
type UpdateTagValueRequest struct {
ID *uint `json:"id"`
Value *string `json:"value"`
TagID *uint `json:"tag_id"`
Active *bool `json:"active"`
}

View File

@@ -53,5 +53,11 @@ func HandleErrorResponse(c *gin.Context, err error) {
return
}
var invalidInputError *customErrors.InvalidInputError
if errors.As(err, &invalidInputError) {
c.JSON(http.StatusBadRequest, ErrorResponse(err, http.StatusBadRequest, nil))
return
}
c.JSON(http.StatusInternalServerError, ErrorResponse(err, http.StatusInternalServerError, nil))
}

View File

@@ -0,0 +1,8 @@
package tagValue
type TagValueResponse struct {
ID uint `json:"id"`
TagID uint `json:"tag_id"`
Value string `json:"value"`
Active bool `json:"active"`
}

View File

@@ -2,14 +2,22 @@ package tagValue
import (
"fmt"
"gorm.io/gorm"
"houston/common/util/dto"
"houston/logger"
"houston/model/customErrors"
tagModel "houston/model/tag"
tagValueModel "houston/model/tagValue"
"houston/repository/tagValue"
tagValueRequest "houston/service/request/tagValue"
tagValueResponse "houston/service/response/tagValue"
tagService "houston/service/tag"
"time"
)
type tagValueServiceImpl struct {
tagValueRepository tagValue.TagValueRepository
tagService tagService.ITagService
}
const logTag = "[tag-value-service]"
@@ -25,3 +33,144 @@ func (service *tagValueServiceImpl) GetTagValuesByTagName(tagName string) ([]tag
return dto.ToDtoArray[tagValueModel.TagValueEntity, tagValueModel.TagValueDTO](tagValues), nil
}
func (service *tagValueServiceImpl) AddTagValue(request tagValueRequest.AddTagValueRequest) (*tagValueResponse.TagValueResponse, error) {
logger.Info(fmt.Sprintf("%s received request to create tag value: %v", logTag, request))
tag, err := service.tagService.FindTagById(*request.TagId)
if err != nil {
logger.Error(fmt.Sprintf("%s Error while fetching tag by id: %d : %v", logTag, *request.TagId, err))
return nil, err
} else {
err := validateTag(tag)
if err != nil {
logger.Info(fmt.Sprintf("%s Invalid tag: %v : %v", logTag, tag, err))
return nil, err
}
}
tagValue, err := service.tagValueRepository.AddTagValue(*request.TagId, *request.Value)
if err != nil {
logger.Info(fmt.Sprintf("%s Error while creating tag value: %v", logTag, err))
return nil, err
}
return convertToTagValueResponse(*tagValue), nil
}
func (service *tagValueServiceImpl) UpdateTagValue(request tagValueRequest.UpdateTagValueRequest) (*tagValueResponse.TagValueResponse, error) {
logger.Info(fmt.Sprintf("%s received request to update tag value: %v", logTag, request))
tagValue, err := service.tagValueRepository.GetTagValueById(*request.ID)
if err != nil {
logger.Error(fmt.Sprintf("%s Error while fetching tag value by id: %d : %v", logTag, *request.ID, err))
return nil, err
} else if !tagValue.Tag.Active {
logger.Info(fmt.Sprintf("%s Tag is inactive: %v", logTag, tagValue.Tag))
return nil, customErrors.NewInvalidInputError("Current Tag is inactive for given tag value")
}
if request.TagID != nil {
err := service.updateTagId(tagValue, *request.TagID)
if err != nil {
return nil, err
}
}
if request.Value != nil {
err := service.updateValue(tagValue, *request.Value)
if err != nil {
return nil, err
}
}
if request.Active != nil {
err := service.updateActiveStatus(tagValue, *request.Active)
if err != nil {
return nil, err
}
}
err = service.tagValueRepository.UpdateTagValue(tagValue)
if err != nil {
logger.Error(fmt.Sprintf("%s Error while updating tag value: %v", logTag, err))
return nil, err
}
return convertToTagValueResponse(*tagValue), nil
}
func (service *tagValueServiceImpl) updateTagId(tagValue *tagValueModel.TagValueEntity, tagId uint) error {
if tagValue.TagId == tagId {
return customErrors.NewInvalidInputError("tag id is same as existing tag id")
} else if !tagValue.Active {
return customErrors.NewInvalidInputError("Tag value is inactive. Please activate it first")
}
tag, err := service.tagService.FindTagById(tagId)
if err != nil {
logger.Error(fmt.Sprintf("%s Error while fetching tag by id: %d : %v", logTag, tagId, err))
return err
} else {
err := validateTag(tag)
if err != nil {
logger.Info(fmt.Sprintf("%s Invalid tag: %v : %v", logTag, tag, err))
return err
}
}
tagValue.TagId = tagId
tagValue.Tag = *tag
tagValue.UpdatedAt = time.Now()
return nil
}
func (service *tagValueServiceImpl) updateValue(tagValue *tagValueModel.TagValueEntity, value string) error {
if tagValue.Value == value {
return customErrors.NewInvalidInputError("tag value is same as existing value")
} else if !tagValue.Active {
return customErrors.NewInvalidInputError("Tag value is inactive. Please activate it first")
}
tagValue.Value = value
tagValue.UpdatedAt = time.Now()
return nil
}
func (service *tagValueServiceImpl) updateActiveStatus(tagValue *tagValueModel.TagValueEntity, isActive bool) error {
if tagValue.Active == isActive {
return customErrors.NewInvalidInputError("tag value activation status is same as existing status")
}
tagValue.Active = isActive
tagValue.UpdatedAt = time.Now()
if tagValue.Active {
tagValue.DeletedAt = gorm.DeletedAt{Time: time.Time{}, Valid: false}
} else {
tagValue.DeletedAt = gorm.DeletedAt{Time: time.Now(), Valid: true}
}
return nil
}
func validateTag(tag *tagModel.TagEntity) error {
if tag == nil {
return customErrors.NewNotFoundError("Tag not found")
} else if !tag.Active {
return customErrors.NewInvalidInputError("Tag is inactive")
} else if tag.Type == tagModel.FreeText {
return customErrors.NewInvalidInputError("Tag type is free text")
}
return nil
}
func convertToTagValueResponse(tagValue tagValueModel.TagValueEntity) *tagValueResponse.TagValueResponse {
return &tagValueResponse.TagValueResponse{
ID: tagValue.ID,
TagID: tagValue.TagId,
Value: tagValue.Value,
Active: tagValue.Active,
}
}

View File

@@ -3,12 +3,17 @@ package tagValue
import (
tagValueModel "houston/model/tagValue"
"houston/repository/tagValue"
tagValueRequest "houston/service/request/tagValue"
tagValueResponse "houston/service/response/tagValue"
"houston/service/tag"
)
type TagValueService interface {
GetTagValuesByTagName(tagName string) ([]tagValueModel.TagValueDTO, error)
AddTagValue(request tagValueRequest.AddTagValueRequest) (*tagValueResponse.TagValueResponse, error)
UpdateTagValue(request tagValueRequest.UpdateTagValueRequest) (*tagValueResponse.TagValueResponse, error)
}
func NewTagValueService(tagValueRepository tagValue.TagValueRepository) TagValueService {
return &tagValueServiceImpl{tagValueRepository: tagValueRepository}
func NewTagValueService(tagValueRepository tagValue.TagValueRepository, tagService tag.ITagService) TagValueService {
return &tagValueServiceImpl{tagValueRepository: tagValueRepository, tagService: tagService}
}

View File

@@ -0,0 +1,299 @@
package tagValue
import (
"errors"
"github.com/stretchr/testify/suite"
"gorm.io/gorm"
"houston/logger"
"houston/mocks"
"houston/model/tag"
tagValueModel "houston/model/tagValue"
tagValueRequest "houston/service/request/tagValue"
"testing"
"time"
)
type TagValueServiceSuite struct {
suite.Suite
tagValueRepo mocks.TagValueRepositoryMock
tagService mocks.ITagServiceMock
tagValueService TagValueService
}
func (suite *TagValueServiceSuite) Test_GetTagValuesByTagName_GetTagValuesByTagNameError() {
suite.tagValueRepo.GetTagValuesByTagNameMock.Return(nil, errors.New("error"))
response, err := suite.tagValueService.GetTagValuesByTagName("tag")
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_GetTagValuesByTagName_SuccessCase() {
suite.tagValueRepo.GetTagValuesByTagNameMock.Return([]tagValueModel.TagValueEntity{*getMockTagValue()}, nil)
response, err := suite.tagValueService.GetTagValuesByTagName("tag")
suite.NotNil(response, "response should not be nil")
suite.Nil(err, "error should be nil")
}
func (suite *TagValueServiceSuite) Test_AddTagValue_FindTagError() {
suite.tagService.FindTagByIdMock.Return(nil, errors.New("error"))
response, err := suite.tagValueService.AddTagValue(getMockAddTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_AddTagValue_NilTagCase() {
suite.tagService.FindTagByIdMock.Return(nil, nil)
response, err := suite.tagValueService.AddTagValue(getMockAddTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_AddTagValue_InactiveTagCase() {
suite.tagService.FindTagByIdMock.Return(getMockTag(false, tag.SingleValue), nil)
response, err := suite.tagValueService.AddTagValue(getMockAddTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_AddTagValue_FreeTextTagTypeCase() {
suite.tagService.FindTagByIdMock.Return(getMockTag(true, tag.FreeText), nil)
response, err := suite.tagValueService.AddTagValue(getMockAddTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_AddTagValue_AddTagValueError() {
suite.tagService.FindTagByIdMock.Return(getMockTag(true, tag.SingleValue), nil)
suite.tagValueRepo.AddTagValueMock.Return(nil, errors.New("error"))
response, err := suite.tagValueService.AddTagValue(getMockAddTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_AddTagValue_SuccessCase() {
suite.tagService.FindTagByIdMock.Return(getMockTag(true, tag.SingleValue), nil)
suite.tagValueRepo.AddTagValueMock.Return(getMockTagValue(), nil)
response, err := suite.tagValueService.AddTagValue(getMockAddTagValueRequest())
suite.NotNil(response, "response should not be nil")
suite.Nil(err, "error should be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_GetTagValueError() {
suite.tagValueRepo.GetTagValueByIdMock.Return(nil, errors.New("error"))
response, err := suite.tagValueService.UpdateTagValue(getMockUpdateTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_CurrentInactiveTagCase() {
tagValue := getMockTagValue()
tagValue.Tag.Active = false
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
response, err := suite.tagValueService.UpdateTagValue(getMockUpdateTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_SameTagIDCase() {
tagValue := getMockTagValue()
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
tagValueId, tagId := uint(1), uint(2)
response, err := suite.tagValueService.UpdateTagValue(tagValueRequest.UpdateTagValueRequest{
ID: &tagValueId, TagID: &tagId,
})
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_SameValueCase() {
tagValue := getMockTagValue()
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
tagValueId, value := uint(1), "test value"
response, err := suite.tagValueService.UpdateTagValue(tagValueRequest.UpdateTagValueRequest{
ID: &tagValueId, Value: &value,
})
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_SameActivationStateCase() {
tagValue := getMockTagValue()
tagValue.Active = true
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
tagValueId, active := uint(1), true
response, err := suite.tagValueService.UpdateTagValue(tagValueRequest.UpdateTagValueRequest{
ID: &tagValueId, Active: &active,
})
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_FindTagError() {
tagValue := getMockTagValue()
tagValue.Active = true
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagService.FindTagByIdMock.Return(nil, errors.New("error"))
response, err := suite.tagValueService.UpdateTagValue(getMockUpdateTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_UpdateValueInactiveTagValueCase() {
tagValue := getMockTagValue()
request := getMockUpdateTagValueRequest()
request.TagID = nil
request.Active = nil
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagService.FindTagByIdMock.Return(getMockTag(true, tag.SingleValue), nil)
response, err := suite.tagValueService.UpdateTagValue(request)
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_NilTagCase() {
tagValue := getMockTagValue()
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagService.FindTagByIdMock.Return(nil, nil)
response, err := suite.tagValueService.UpdateTagValue(getMockUpdateTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_InactiveTagCase() {
tagValue := getMockTagValue()
tagValue.Active = true
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagService.FindTagByIdMock.Return(getMockTag(false, tag.SingleValue), nil)
response, err := suite.tagValueService.UpdateTagValue(getMockUpdateTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_FreeTextTagTypeCase() {
tagValue := getMockTagValue()
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagService.FindTagByIdMock.Return(getMockTag(true, tag.FreeText), nil)
response, err := suite.tagValueService.UpdateTagValue(getMockUpdateTagValueRequest())
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_UpdateTagValueError() {
tagValue := getMockTagValue()
tagValue.Active = true
request := getMockUpdateTagValueRequest()
request.Active = nil
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagService.FindTagByIdMock.Return(getMockTag(true, tag.SingleValue), nil)
suite.tagValueRepo.UpdateTagValueMock.Return(errors.New("error"))
response, err := suite.tagValueService.UpdateTagValue(request)
suite.Nil(response, "response should be nil")
suite.NotNil(err, "error should not be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_SuccessCase() {
tagValue := getMockTagValue()
tagValue.Active = true
mockRequest := getMockUpdateTagValueRequest()
mockRequest.Active = nil
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagService.FindTagByIdMock.Return(getMockTag(true, tag.SingleValue), nil)
suite.tagValueRepo.UpdateTagValueMock.Return(nil)
response, err := suite.tagValueService.UpdateTagValue(mockRequest)
suite.NotNil(response, "response should not be nil")
suite.Nil(err, "error should be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_DeactivationCase() {
tagValue := getMockTagValue()
tagValue.Active = true
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagValueRepo.UpdateTagValueMock.Return(nil)
tagValueId, isActive := uint(1), false
response, err := suite.tagValueService.UpdateTagValue(tagValueRequest.UpdateTagValueRequest{
ID: &tagValueId, Active: &isActive,
})
suite.NotNil(response, "response should not be nil")
suite.Nil(err, "error should be nil")
}
func (suite *TagValueServiceSuite) Test_UpdateTagValue_ReactivationCase() {
tagValue := getMockTagValue()
suite.tagValueRepo.GetTagValueByIdMock.Return(tagValue, nil)
suite.tagValueRepo.UpdateTagValueMock.Return(nil)
tagValueId, isActive := uint(1), true
response, err := suite.tagValueService.UpdateTagValue(tagValueRequest.UpdateTagValueRequest{
ID: &tagValueId, Active: &isActive,
})
suite.NotNil(response, "response should not be nil")
suite.Nil(err, "error should be nil")
}
func (suite *TagValueServiceSuite) SetupTest() {
logger.InitLogger()
suite.tagValueRepo = *mocks.NewTagValueRepositoryMock(suite.T())
suite.tagService = *mocks.NewITagServiceMock(suite.T())
suite.tagValueService = NewTagValueService(&suite.tagValueRepo, &suite.tagService)
}
func TestTagValueServiceSuite(t *testing.T) {
suite.Run(t, new(TagValueServiceSuite))
}
func getMockAddTagValueRequest() tagValueRequest.AddTagValueRequest {
tagId, value := uint(1), "value"
return tagValueRequest.AddTagValueRequest{
TagId: &tagId, Value: &value,
}
}
func getMockUpdateTagValueRequest() tagValueRequest.UpdateTagValueRequest {
tagId, value, active := uint(1), "value", true
return tagValueRequest.UpdateTagValueRequest{
ID: &tagId, TagID: &tagId, Value: &value, Active: &active,
}
}
func getMockTag(active bool, tagType tag.Type) *tag.TagEntity {
return &tag.TagEntity{
Name: "tag", Active: active, Type: tagType,
}
}
func getMockTagValue() *tagValueModel.TagValueEntity {
return &tagValueModel.TagValueEntity{
ID: 0,
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
DeletedAt: gorm.DeletedAt{},
TagId: 2,
Value: "test value",
Active: false,
Tag: tag.TagEntity{
Active: true,
},
}
}