Update
This commit is contained in:
@@ -15,25 +15,26 @@ import (
|
||||
)
|
||||
|
||||
type LetterProcessorImpl struct {
|
||||
letterRepo *repository.LetterIncomingRepository
|
||||
attachRepo *repository.LetterIncomingAttachmentRepository
|
||||
txManager *repository.TxManager
|
||||
activity *ActivityLogProcessorImpl
|
||||
// new repos for dispositions
|
||||
dispositionRepo *repository.LetterDispositionRepository
|
||||
letterRepo *repository.LetterIncomingRepository
|
||||
attachRepo *repository.LetterIncomingAttachmentRepository
|
||||
txManager *repository.TxManager
|
||||
activity *ActivityLogProcessorImpl
|
||||
dispositionRepo *repository.LetterIncomingDispositionRepository
|
||||
dispositionDeptRepo *repository.LetterIncomingDispositionDepartmentRepository
|
||||
dispositionActionSelRepo *repository.LetterDispositionActionSelectionRepository
|
||||
dispositionNoteRepo *repository.DispositionNoteRepository
|
||||
// discussion repo
|
||||
discussionRepo *repository.LetterDiscussionRepository
|
||||
// settings and recipients
|
||||
settingRepo *repository.AppSettingRepository
|
||||
recipientRepo *repository.LetterIncomingRecipientRepository
|
||||
departmentRepo *repository.DepartmentRepository
|
||||
userDeptRepo *repository.UserDepartmentRepository
|
||||
discussionRepo *repository.LetterDiscussionRepository
|
||||
settingRepo *repository.AppSettingRepository
|
||||
recipientRepo *repository.LetterIncomingRecipientRepository
|
||||
departmentRepo *repository.DepartmentRepository
|
||||
userDeptRepo *repository.UserDepartmentRepository
|
||||
priorityRepo *repository.PriorityRepository
|
||||
institutionRepo *repository.InstitutionRepository
|
||||
dispActionRepo *repository.DispositionActionRepository
|
||||
}
|
||||
|
||||
func NewLetterProcessor(letterRepo *repository.LetterIncomingRepository, attachRepo *repository.LetterIncomingAttachmentRepository, txManager *repository.TxManager, activity *ActivityLogProcessorImpl, dispRepo *repository.LetterDispositionRepository, dispSelRepo *repository.LetterDispositionActionSelectionRepository, noteRepo *repository.DispositionNoteRepository, discussionRepo *repository.LetterDiscussionRepository, settingRepo *repository.AppSettingRepository, recipientRepo *repository.LetterIncomingRecipientRepository, departmentRepo *repository.DepartmentRepository, userDeptRepo *repository.UserDepartmentRepository) *LetterProcessorImpl {
|
||||
return &LetterProcessorImpl{letterRepo: letterRepo, attachRepo: attachRepo, txManager: txManager, activity: activity, dispositionRepo: dispRepo, dispositionActionSelRepo: dispSelRepo, dispositionNoteRepo: noteRepo, discussionRepo: discussionRepo, settingRepo: settingRepo, recipientRepo: recipientRepo, departmentRepo: departmentRepo, userDeptRepo: userDeptRepo}
|
||||
func NewLetterProcessor(letterRepo *repository.LetterIncomingRepository, attachRepo *repository.LetterIncomingAttachmentRepository, txManager *repository.TxManager, activity *ActivityLogProcessorImpl, dispRepo *repository.LetterIncomingDispositionRepository, dispDeptRepo *repository.LetterIncomingDispositionDepartmentRepository, dispSelRepo *repository.LetterDispositionActionSelectionRepository, noteRepo *repository.DispositionNoteRepository, discussionRepo *repository.LetterDiscussionRepository, settingRepo *repository.AppSettingRepository, recipientRepo *repository.LetterIncomingRecipientRepository, departmentRepo *repository.DepartmentRepository, userDeptRepo *repository.UserDepartmentRepository, priorityRepo *repository.PriorityRepository, institutionRepo *repository.InstitutionRepository, dispActionRepo *repository.DispositionActionRepository) *LetterProcessorImpl {
|
||||
return &LetterProcessorImpl{letterRepo: letterRepo, attachRepo: attachRepo, txManager: txManager, activity: activity, dispositionRepo: dispRepo, dispositionDeptRepo: dispDeptRepo, dispositionActionSelRepo: dispSelRepo, dispositionNoteRepo: noteRepo, discussionRepo: discussionRepo, settingRepo: settingRepo, recipientRepo: recipientRepo, departmentRepo: departmentRepo, userDeptRepo: userDeptRepo, priorityRepo: priorityRepo, institutionRepo: institutionRepo, dispActionRepo: dispActionRepo}
|
||||
}
|
||||
|
||||
func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *contract.CreateIncomingLetterRequest) (*contract.IncomingLetterResponse, error) {
|
||||
@@ -85,7 +86,6 @@ func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *con
|
||||
}
|
||||
}
|
||||
|
||||
// resolve department codes to ids using repository
|
||||
depIDs := make([]uuid.UUID, 0, len(defaultDeptCodes))
|
||||
for _, code := range defaultDeptCodes {
|
||||
dep, err := p.departmentRepo.GetByCode(txCtx, code)
|
||||
@@ -94,20 +94,19 @@ func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *con
|
||||
}
|
||||
depIDs = append(depIDs, dep.ID)
|
||||
}
|
||||
// query user memberships for all departments at once
|
||||
|
||||
userMemberships, _ := p.userDeptRepo.ListActiveByDepartmentIDs(txCtx, depIDs)
|
||||
// build recipients: one department recipient per department + one user recipient per membership
|
||||
recipients := make([]entities.LetterIncomingRecipient, 0, len(depIDs)+len(userMemberships))
|
||||
// department recipients
|
||||
for _, depID := range depIDs {
|
||||
id := depID
|
||||
recipients = append(recipients, entities.LetterIncomingRecipient{LetterID: entity.ID, RecipientDepartmentID: &id, Status: entities.RecipientStatusNew})
|
||||
}
|
||||
// user recipients
|
||||
var recipients []entities.LetterIncomingRecipient
|
||||
|
||||
mapsUsers := map[string]bool{}
|
||||
for _, row := range userMemberships {
|
||||
uid := row.UserID
|
||||
recipients = append(recipients, entities.LetterIncomingRecipient{LetterID: entity.ID, RecipientUserID: &uid, Status: entities.RecipientStatusNew})
|
||||
if _, ok := mapsUsers[uid.String()]; !ok {
|
||||
recipients = append(recipients, entities.LetterIncomingRecipient{LetterID: entity.ID, RecipientUserID: &uid, RecipientDepartmentID: &row.DepartmentID, Status: entities.RecipientStatusNew})
|
||||
}
|
||||
mapsUsers[uid.String()] = true
|
||||
}
|
||||
|
||||
if len(recipients) > 0 {
|
||||
if err := p.recipientRepo.CreateBulk(txCtx, recipients); err != nil {
|
||||
return err
|
||||
@@ -141,9 +140,26 @@ func (p *LetterProcessorImpl) CreateIncomingLetter(ctx context.Context, req *con
|
||||
}
|
||||
|
||||
savedAttachments, _ := p.attachRepo.ListByLetter(txCtx, entity.ID)
|
||||
result = transformer.LetterEntityToContract(entity, savedAttachments)
|
||||
var pr *entities.Priority
|
||||
if entity.PriorityID != nil {
|
||||
if p.priorityRepo != nil {
|
||||
if got, err := p.priorityRepo.Get(txCtx, *entity.PriorityID); err == nil {
|
||||
pr = got
|
||||
}
|
||||
}
|
||||
}
|
||||
var inst *entities.Institution
|
||||
if entity.SenderInstitutionID != nil {
|
||||
if p.institutionRepo != nil {
|
||||
if got, err := p.institutionRepo.Get(txCtx, *entity.SenderInstitutionID); err == nil {
|
||||
inst = got
|
||||
}
|
||||
}
|
||||
}
|
||||
result = transformer.LetterEntityToContract(entity, savedAttachments, pr, inst)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -156,7 +172,19 @@ func (p *LetterProcessorImpl) GetIncomingLetterByID(ctx context.Context, id uuid
|
||||
return nil, err
|
||||
}
|
||||
atts, _ := p.attachRepo.ListByLetter(ctx, id)
|
||||
return transformer.LetterEntityToContract(entity, atts), nil
|
||||
var pr *entities.Priority
|
||||
if entity.PriorityID != nil && p.priorityRepo != nil {
|
||||
if got, err := p.priorityRepo.Get(ctx, *entity.PriorityID); err == nil {
|
||||
pr = got
|
||||
}
|
||||
}
|
||||
var inst *entities.Institution
|
||||
if entity.SenderInstitutionID != nil && p.institutionRepo != nil {
|
||||
if got, err := p.institutionRepo.Get(ctx, *entity.SenderInstitutionID); err == nil {
|
||||
inst = got
|
||||
}
|
||||
}
|
||||
return transformer.LetterEntityToContract(entity, atts, pr, inst), nil
|
||||
}
|
||||
|
||||
func (p *LetterProcessorImpl) ListIncomingLetters(ctx context.Context, req *contract.ListIncomingLettersRequest) (*contract.ListIncomingLettersResponse, error) {
|
||||
@@ -175,7 +203,19 @@ func (p *LetterProcessorImpl) ListIncomingLetters(ctx context.Context, req *cont
|
||||
respList := make([]contract.IncomingLetterResponse, 0, len(list))
|
||||
for _, e := range list {
|
||||
atts, _ := p.attachRepo.ListByLetter(ctx, e.ID)
|
||||
resp := transformer.LetterEntityToContract(&e, atts)
|
||||
var pr *entities.Priority
|
||||
if e.PriorityID != nil && p.priorityRepo != nil {
|
||||
if got, err := p.priorityRepo.Get(ctx, *e.PriorityID); err == nil {
|
||||
pr = got
|
||||
}
|
||||
}
|
||||
var inst *entities.Institution
|
||||
if e.SenderInstitutionID != nil && p.institutionRepo != nil {
|
||||
if got, err := p.institutionRepo.Get(ctx, *e.SenderInstitutionID); err == nil {
|
||||
inst = got
|
||||
}
|
||||
}
|
||||
resp := transformer.LetterEntityToContract(&e, atts, pr, inst)
|
||||
respList = append(respList, *resp)
|
||||
}
|
||||
return &contract.ListIncomingLettersResponse{Letters: respList, Pagination: transformer.CreatePaginationResponse(int(total), page, limit)}, nil
|
||||
@@ -225,7 +265,19 @@ func (p *LetterProcessorImpl) UpdateIncomingLetter(ctx context.Context, id uuid.
|
||||
}
|
||||
}
|
||||
atts, _ := p.attachRepo.ListByLetter(txCtx, id)
|
||||
out = transformer.LetterEntityToContract(entity, atts)
|
||||
var pr *entities.Priority
|
||||
if entity.PriorityID != nil && p.priorityRepo != nil {
|
||||
if got, err := p.priorityRepo.Get(txCtx, *entity.PriorityID); err == nil {
|
||||
pr = got
|
||||
}
|
||||
}
|
||||
var inst *entities.Institution
|
||||
if entity.SenderInstitutionID != nil && p.institutionRepo != nil {
|
||||
if got, err := p.institutionRepo.Get(txCtx, *entity.SenderInstitutionID); err == nil {
|
||||
inst = got
|
||||
}
|
||||
}
|
||||
out = transformer.LetterEntityToContract(entity, atts, pr, inst)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -254,48 +306,53 @@ func (p *LetterProcessorImpl) CreateDispositions(ctx context.Context, req *contr
|
||||
var out *contract.ListDispositionsResponse
|
||||
err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
userID := appcontext.FromGinContext(txCtx).UserID
|
||||
created := make([]entities.LetterDisposition, 0, len(req.ToDepartmentIDs))
|
||||
|
||||
disp := entities.LetterIncomingDisposition{
|
||||
LetterID: req.LetterID,
|
||||
DepartmentID: &req.FromDepartment,
|
||||
Notes: req.Notes,
|
||||
CreatedBy: userID,
|
||||
}
|
||||
if err := p.dispositionRepo.Create(txCtx, &disp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dispDepartments []entities.LetterIncomingDispositionDepartment
|
||||
for _, toDept := range req.ToDepartmentIDs {
|
||||
disp := entities.LetterDisposition{
|
||||
LetterID: req.LetterID,
|
||||
FromDepartmentID: nil,
|
||||
ToDepartmentID: &toDept,
|
||||
Notes: req.Notes,
|
||||
Status: entities.DispositionPending,
|
||||
CreatedBy: userID,
|
||||
dispDepartments = append(dispDepartments, entities.LetterIncomingDispositionDepartment{
|
||||
LetterIncomingDispositionID: disp.ID,
|
||||
DepartmentID: toDept,
|
||||
})
|
||||
}
|
||||
|
||||
if err := p.dispositionDeptRepo.CreateBulk(txCtx, dispDepartments); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(req.SelectedActions) > 0 {
|
||||
selections := make([]entities.LetterDispositionActionSelection, 0, len(req.SelectedActions))
|
||||
for _, sel := range req.SelectedActions {
|
||||
selections = append(selections, entities.LetterDispositionActionSelection{
|
||||
DispositionID: disp.ID,
|
||||
ActionID: sel.ActionID,
|
||||
Note: sel.Note,
|
||||
CreatedBy: userID,
|
||||
})
|
||||
}
|
||||
if err := p.dispositionRepo.Create(txCtx, &disp); err != nil {
|
||||
if err := p.dispositionActionSelRepo.CreateBulk(txCtx, selections); err != nil {
|
||||
return err
|
||||
}
|
||||
created = append(created, disp)
|
||||
|
||||
if len(req.SelectedActions) > 0 {
|
||||
selections := make([]entities.LetterDispositionActionSelection, 0, len(req.SelectedActions))
|
||||
for _, sel := range req.SelectedActions {
|
||||
selections = append(selections, entities.LetterDispositionActionSelection{
|
||||
DispositionID: disp.ID,
|
||||
ActionID: sel.ActionID,
|
||||
Note: sel.Note,
|
||||
CreatedBy: userID,
|
||||
})
|
||||
}
|
||||
if err := p.dispositionActionSelRepo.CreateBulk(txCtx, selections); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if p.activity != nil {
|
||||
action := "disposition.created"
|
||||
for _, d := range created {
|
||||
ctxMap := map[string]interface{}{"to_department_id": d.ToDepartmentID}
|
||||
if err := p.activity.Log(txCtx, req.LetterID, action, &userID, nil, nil, &d.ID, nil, nil, ctxMap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out = &contract.ListDispositionsResponse{Dispositions: transformer.DispositionsToContract(created)}
|
||||
if p.activity != nil {
|
||||
action := "disposition.created"
|
||||
ctxMap := map[string]interface{}{"to_department_id": dispDepartments}
|
||||
if err := p.activity.Log(txCtx, req.LetterID, action, &userID, nil, nil, &disp.ID, nil, nil, ctxMap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
out = &contract.ListDispositionsResponse{Dispositions: []contract.DispositionResponse{transformer.DispoToContract(disp)}}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -312,6 +369,64 @@ func (p *LetterProcessorImpl) ListDispositionsByLetter(ctx context.Context, lett
|
||||
return &contract.ListDispositionsResponse{Dispositions: transformer.DispositionsToContract(list)}, nil
|
||||
}
|
||||
|
||||
func (p *LetterProcessorImpl) GetEnhancedDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListEnhancedDispositionsResponse, error) {
|
||||
// Get dispositions with all related data preloaded in a single query
|
||||
dispositions, err := p.dispositionRepo.ListByLetter(ctx, letterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get discussions with preloaded user profiles
|
||||
discussions, err := p.discussionRepo.ListByLetter(ctx, letterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract all mentioned user IDs from discussions for efficient batch fetching
|
||||
var mentionedUserIDs []uuid.UUID
|
||||
mentionedUserIDsMap := make(map[uuid.UUID]bool)
|
||||
|
||||
for _, discussion := range discussions {
|
||||
if discussion.Mentions != nil {
|
||||
mentions := map[string]interface{}(discussion.Mentions)
|
||||
if userIDs, ok := mentions["user_ids"]; ok {
|
||||
if userIDList, ok := userIDs.([]interface{}); ok {
|
||||
for _, userID := range userIDList {
|
||||
if userIDStr, ok := userID.(string); ok {
|
||||
if userUUID, err := uuid.Parse(userIDStr); err == nil {
|
||||
if !mentionedUserIDsMap[userUUID] {
|
||||
mentionedUserIDsMap[userUUID] = true
|
||||
mentionedUserIDs = append(mentionedUserIDs, userUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all mentioned users in a single batch query
|
||||
var mentionedUsers []entities.User
|
||||
if len(mentionedUserIDs) > 0 {
|
||||
mentionedUsers, err = p.discussionRepo.GetUsersByIDs(ctx, mentionedUserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Transform dispositions
|
||||
enhancedDispositions := transformer.EnhancedDispositionsWithPreloadedDataToContract(dispositions)
|
||||
|
||||
// Transform discussions with mentioned users
|
||||
enhancedDiscussions := transformer.DiscussionsWithPreloadedDataToContract(discussions, mentionedUsers)
|
||||
|
||||
return &contract.ListEnhancedDispositionsResponse{
|
||||
Dispositions: enhancedDispositions,
|
||||
Discussions: enhancedDiscussions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *LetterProcessorImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
|
||||
var out *contract.LetterDiscussionResponse
|
||||
err := p.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
@@ -320,7 +435,7 @@ func (p *LetterProcessorImpl) CreateDiscussion(ctx context.Context, letterID uui
|
||||
if req.Mentions != nil {
|
||||
mentions = entities.JSONB(req.Mentions)
|
||||
}
|
||||
disc := &entities.LetterDiscussion{LetterID: letterID, ParentID: req.ParentID, UserID: userID, Message: req.Message, Mentions: mentions}
|
||||
disc := &entities.LetterDiscussion{ID: uuid.New(), LetterID: letterID, ParentID: req.ParentID, UserID: userID, Message: req.Message, Mentions: mentions}
|
||||
if err := p.discussionRepo.Create(txCtx, disc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -112,9 +112,11 @@ func (p *UserProcessorImpl) GetUserByID(ctx context.Context, id uuid.UUID) (*con
|
||||
}
|
||||
resp := transformer.EntityToContract(user)
|
||||
if resp != nil {
|
||||
// Roles are loaded separately since they're not preloaded
|
||||
if roles, err := p.userRepo.GetRolesByUserID(ctx, resp.ID); err == nil {
|
||||
resp.Roles = transformer.RolesToContract(roles)
|
||||
}
|
||||
// Departments are now preloaded, so they're already in the response
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -125,6 +127,7 @@ func (p *UserProcessorImpl) GetUserByEmail(ctx context.Context, email string) (*
|
||||
return nil, fmt.Errorf("user not found: %w", err)
|
||||
}
|
||||
|
||||
// Departments are now preloaded, so they're already in the response
|
||||
return transformer.EntityToContract(user), nil
|
||||
}
|
||||
|
||||
@@ -149,6 +152,7 @@ func (p *UserProcessorImpl) ListUsersWithFilters(ctx context.Context, req *contr
|
||||
for i := range responses {
|
||||
userIDs = append(userIDs, responses[i].ID)
|
||||
}
|
||||
// Roles are loaded separately since they're not preloaded
|
||||
rolesMap, err := p.userRepo.GetRolesByUserIDs(ctx, userIDs)
|
||||
if err == nil {
|
||||
for i := range responses {
|
||||
@@ -157,6 +161,7 @@ func (p *UserProcessorImpl) ListUsersWithFilters(ctx context.Context, req *contr
|
||||
}
|
||||
}
|
||||
}
|
||||
// Departments are now preloaded, so they're already in the responses
|
||||
return responses, int(totalCount), nil
|
||||
}
|
||||
|
||||
@@ -272,3 +277,38 @@ func (p *UserProcessorImpl) UpdateUserProfile(ctx context.Context, userID uuid.U
|
||||
}
|
||||
return transformer.ProfileEntityToContract(entity), nil
|
||||
}
|
||||
|
||||
// GetActiveUsersForMention retrieves active users for mention purposes with optional username search
|
||||
func (p *UserProcessorImpl) GetActiveUsersForMention(ctx context.Context, search *string, limit int) ([]contract.UserResponse, error) {
|
||||
if limit <= 0 {
|
||||
limit = 50 // Default limit for mention suggestions
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100 // Max limit for mention suggestions
|
||||
}
|
||||
|
||||
// Set isActive to true to only get active users
|
||||
isActive := true
|
||||
users, _, err := p.userRepo.ListWithFilters(ctx, search, nil, &isActive, limit, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get active users: %w", err)
|
||||
}
|
||||
|
||||
responses := transformer.EntitiesToContracts(users)
|
||||
userIDs := make([]uuid.UUID, 0, len(responses))
|
||||
for i := range responses {
|
||||
userIDs = append(userIDs, responses[i].ID)
|
||||
}
|
||||
|
||||
// Load roles for the users
|
||||
rolesMap, err := p.userRepo.GetRolesByUserIDs(ctx, userIDs)
|
||||
if err == nil {
|
||||
for i := range responses {
|
||||
if roles, ok := rolesMap[responses[i].ID]; ok {
|
||||
responses[i].Roles = transformer.RolesToContract(roles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"eslogad-be/internal/contract"
|
||||
"eslogad-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockUserRepository is a mock implementation of UserRepository
|
||||
type MockUserRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) Create(ctx context.Context, user *entities.User) error {
|
||||
args := m.Called(ctx, user)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.User, error) {
|
||||
args := m.Called(ctx, id)
|
||||
return args.Get(0).(*entities.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) GetByEmail(ctx context.Context, email string) (*entities.User, error) {
|
||||
args := m.Called(ctx, email)
|
||||
return args.Get(0).(*entities.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) GetByRole(ctx context.Context, role entities.UserRole) ([]*entities.User, error) {
|
||||
args := m.Called(ctx, role)
|
||||
return args.Get(0).([]*entities.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) GetActiveUsers(ctx context.Context, organizationID uuid.UUID) ([]*entities.User, error) {
|
||||
args := m.Called(ctx, organizationID)
|
||||
return args.Get(0).([]*entities.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) Update(ctx context.Context, user *entities.User) error {
|
||||
args := m.Called(ctx, user)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
args := m.Called(ctx, id)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) UpdatePassword(ctx context.Context, id uuid.UUID, passwordHash string) error {
|
||||
args := m.Called(ctx, id, passwordHash)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) UpdateActiveStatus(ctx context.Context, id uuid.UUID, isActive bool) error {
|
||||
args := m.Called(ctx, id, isActive)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.User, int64, error) {
|
||||
args := m.Called(ctx, filters, limit, offset)
|
||||
return args.Get(0).([]*entities.User), args.Get(1).(int64), args.Error(2)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) Count(ctx context.Context, filters map[string]interface{}) (int64, error) {
|
||||
args := m.Called(ctx, filters)
|
||||
return args.Get(0).(int64), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) GetRolesByUserID(ctx context.Context, userID uuid.UUID) ([]entities.Role, error) {
|
||||
args := m.Called(ctx, userID)
|
||||
return args.Get(0).([]entities.Role), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) GetPermissionsByUserID(ctx context.Context, userID uuid.UUID) ([]entities.Permission, error) {
|
||||
args := m.Called(ctx, userID)
|
||||
return args.Get(0).([]entities.Permission), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) GetDepartmentsByUserID(ctx context.Context, userID uuid.UUID) ([]entities.Department, error) {
|
||||
args := m.Called(ctx, userID)
|
||||
return args.Get(0).([]entities.Department), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) GetRolesByUserIDs(ctx context.Context, userIDs []uuid.UUID) (map[uuid.UUID][]entities.Role, error) {
|
||||
args := m.Called(ctx, userIDs)
|
||||
return args.Get(0).(map[uuid.UUID][]entities.Role), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) ListWithFilters(ctx context.Context, search *string, roleCode *string, isActive *bool, limit, offset int) ([]*entities.User, int64, error) {
|
||||
args := m.Called(ctx, search, roleCode, isActive, limit, offset)
|
||||
return args.Get(0).([]*entities.User), args.Get(1).(int64), args.Error(2)
|
||||
}
|
||||
|
||||
// MockUserProfileRepository is a mock implementation of UserProfileRepository
|
||||
type MockUserProfileRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockUserProfileRepository) GetByUserID(ctx context.Context, userID uuid.UUID) (*entities.UserProfile, error) {
|
||||
args := m.Called(ctx, userID)
|
||||
return args.Get(0).(*entities.UserProfile), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserProfileRepository) Create(ctx context.Context, profile *entities.UserProfile) error {
|
||||
args := m.Called(ctx, profile)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserProfileRepository) Upsert(ctx context.Context, profile *entities.UserProfile) error {
|
||||
args := m.Called(ctx, profile)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserProfileRepository) Update(ctx context.Context, profile *entities.UserProfile) error {
|
||||
args := m.Called(ctx, profile)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestGetActiveUsersForMention(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
search *string
|
||||
limit int
|
||||
mockUsers []*entities.User
|
||||
mockRoles map[uuid.UUID][]entities.Role
|
||||
expectedCount int
|
||||
expectedError bool
|
||||
setupMocks func(*MockUserRepository, *MockUserProfileRepository)
|
||||
}{
|
||||
{
|
||||
name: "success with search",
|
||||
search: stringPtr("john"),
|
||||
limit: 10,
|
||||
mockUsers: []*entities.User{
|
||||
{
|
||||
ID: uuid.New(),
|
||||
Name: "John Doe",
|
||||
Email: "john@example.com",
|
||||
IsActive: true,
|
||||
},
|
||||
},
|
||||
expectedCount: 1,
|
||||
expectedError: false,
|
||||
setupMocks: func(mockRepo *MockUserRepository, mockProfileRepo *MockUserProfileRepository) {
|
||||
mockRepo.On("ListWithFilters", mock.Anything, stringPtr("john"), (*string)(nil), boolPtr(true), 10, 0).
|
||||
Return([]*entities.User{
|
||||
{
|
||||
ID: uuid.New(),
|
||||
Name: "John Doe",
|
||||
Email: "john@example.com",
|
||||
IsActive: true,
|
||||
},
|
||||
}, int64(1), nil)
|
||||
|
||||
mockRepo.On("GetRolesByUserIDs", mock.Anything, mock.AnythingOfType("[]uuid.UUID")).
|
||||
Return(map[uuid.UUID][]entities.Role{}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success without search",
|
||||
search: nil,
|
||||
limit: 50,
|
||||
mockUsers: []*entities.User{
|
||||
{
|
||||
ID: uuid.New(),
|
||||
Name: "Jane Doe",
|
||||
Email: "jane@example.com",
|
||||
IsActive: true,
|
||||
},
|
||||
},
|
||||
expectedCount: 1,
|
||||
expectedError: false,
|
||||
setupMocks: func(mockRepo *MockUserRepository, mockProfileRepo *MockUserProfileRepository) {
|
||||
mockRepo.On("ListWithFilters", mock.Anything, (*string)(nil), (*string)(nil), boolPtr(true), 50, 0).
|
||||
Return([]*entities.User{
|
||||
{
|
||||
ID: uuid.New(),
|
||||
Name: "Jane Doe",
|
||||
Email: "jane@example.com",
|
||||
IsActive: true,
|
||||
},
|
||||
}, int64(1), nil)
|
||||
|
||||
mockRepo.On("GetRolesByUserIDs", mock.Anything, mock.AnythingOfType("[]uuid.UUID")).
|
||||
Return(map[uuid.UUID][]entities.Role{}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "limit validation - too high",
|
||||
search: nil,
|
||||
limit: 150,
|
||||
mockUsers: []*entities.User{},
|
||||
expectedCount: 0,
|
||||
expectedError: false,
|
||||
setupMocks: func(mockRepo *MockUserRepository, mockProfileRepo *MockUserProfileRepository) {
|
||||
mockRepo.On("ListWithFilters", mock.Anything, (*string)(nil), (*string)(nil), boolPtr(true), 100, 0).
|
||||
Return([]*entities.User{}, int64(0), nil)
|
||||
|
||||
mockRepo.On("GetRolesByUserIDs", mock.Anything, mock.AnythingOfType("[]uuid.UUID")).
|
||||
Return(map[uuid.UUID][]entities.Role{}, nil)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create mocks
|
||||
mockRepo := &MockUserRepository{}
|
||||
mockProfileRepo := &MockUserProfileRepository{}
|
||||
|
||||
// Setup mocks
|
||||
if tt.setupMocks != nil {
|
||||
tt.setupMocks(mockRepo, mockProfileRepo)
|
||||
}
|
||||
|
||||
// Create processor
|
||||
processor := NewUserProcessor(mockRepo, mockProfileRepo)
|
||||
|
||||
// Call method
|
||||
result, err := processor.GetActiveUsersForMention(context.Background(), tt.search, tt.limit)
|
||||
|
||||
// Assertions
|
||||
if tt.expectedError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, tt.expectedCount)
|
||||
}
|
||||
|
||||
// Verify mocks
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockProfileRepo.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func stringPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
Reference in New Issue
Block a user