This commit is contained in:
Aditya Siregar
2025-08-16 20:37:02 +07:00
parent de60983e4e
commit 1964fe50de
45 changed files with 1732 additions and 251 deletions
@@ -2,6 +2,7 @@ package handler
import (
"context"
"eslogad-be/internal/appcontext"
"eslogad-be/internal/contract"
@@ -71,12 +72,9 @@ func (h *DispositionRouteHandler) Get(c *gin.Context) {
}
func (h *DispositionRouteHandler) ListByFromDept(c *gin.Context) {
fromID, err := uuid.Parse(c.Param("from_department_id"))
if err != nil {
c.JSON(400, &contract.ErrorResponse{Error: "invalid from_department_id", Code: 400})
return
}
resp, err := h.svc.ListByFromDept(c.Request.Context(), fromID)
appCtx := appcontext.FromGinContext(c.Request.Context())
resp, err := h.svc.ListByFromDept(c.Request.Context(), appCtx.DepartmentID)
if err != nil {
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
return
+6 -3
View File
@@ -2,6 +2,7 @@ package handler
import (
"context"
"eslogad-be/internal/appcontext"
"net/http"
"strconv"
@@ -19,7 +20,7 @@ type LetterService interface {
SoftDeleteIncomingLetter(ctx context.Context, id uuid.UUID) error
CreateDispositions(ctx context.Context, req *contract.CreateLetterDispositionRequest) (*contract.ListDispositionsResponse, error)
ListDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListDispositionsResponse, error)
GetEnhancedDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListEnhancedDispositionsResponse, error)
CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error)
UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error)
@@ -112,11 +113,13 @@ func (h *LetterHandler) DeleteIncomingLetter(c *gin.Context) {
}
func (h *LetterHandler) CreateDispositions(c *gin.Context) {
appCtx := appcontext.FromGinContext(c.Request.Context())
var req contract.CreateLetterDispositionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, &contract.ErrorResponse{Error: "invalid body", Code: 400})
return
}
req.FromDepartment = appCtx.DepartmentID
resp, err := h.svc.CreateDispositions(c.Request.Context(), &req)
if err != nil {
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
@@ -125,13 +128,13 @@ func (h *LetterHandler) CreateDispositions(c *gin.Context) {
c.JSON(201, contract.BuildSuccessResponse(resp))
}
func (h *LetterHandler) ListDispositionsByLetter(c *gin.Context) {
func (h *LetterHandler) GetEnhancedDispositionsByLetter(c *gin.Context) {
letterID, err := uuid.Parse(c.Param("letter_id"))
if err != nil {
c.JSON(400, &contract.ErrorResponse{Error: "invalid letter_id", Code: 400})
return
}
resp, err := h.svc.ListDispositionsByLetter(c.Request.Context(), letterID)
resp, err := h.svc.GetEnhancedDispositionsByLetter(c.Request.Context(), letterID)
if err != nil {
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
return
+8 -2
View File
@@ -24,7 +24,7 @@ type MasterService interface {
CreateInstitution(ctx context.Context, req *contract.CreateInstitutionRequest) (*contract.InstitutionResponse, error)
UpdateInstitution(ctx context.Context, id uuid.UUID, req *contract.UpdateInstitutionRequest) (*contract.InstitutionResponse, error)
DeleteInstitution(ctx context.Context, id uuid.UUID) error
ListInstitutions(ctx context.Context) (*contract.ListInstitutionsResponse, error)
ListInstitutions(ctx context.Context, req *contract.ListInstitutionsRequest) (*contract.ListInstitutionsResponse, error)
CreateDispositionAction(ctx context.Context, req *contract.CreateDispositionActionRequest) (*contract.DispositionActionResponse, error)
UpdateDispositionAction(ctx context.Context, id uuid.UUID, req *contract.UpdateDispositionActionRequest) (*contract.DispositionActionResponse, error)
@@ -190,7 +190,13 @@ func (h *MasterHandler) DeleteInstitution(c *gin.Context) {
}
func (h *MasterHandler) ListInstitutions(c *gin.Context) {
resp, err := h.svc.ListInstitutions(c.Request.Context())
var req contract.ListInstitutionsRequest
if search := c.Query("search"); search != "" {
req.Search = &search
}
resp, err := h.svc.ListInstitutions(c.Request.Context(), &req)
if err != nil {
c.JSON(500, &contract.ErrorResponse{Error: err.Error(), Code: 500})
return
+190
View File
@@ -0,0 +1,190 @@
package handler
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"eslogad-be/internal/contract"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockMasterService is a mock implementation of MasterService
type MockMasterService struct {
mock.Mock
}
func (m *MockMasterService) CreateLabel(ctx context.Context, req *contract.CreateLabelRequest) (*contract.LabelResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(*contract.LabelResponse), args.Error(1)
}
func (m *MockMasterService) UpdateLabel(ctx context.Context, id uuid.UUID, req *contract.UpdateLabelRequest) (*contract.LabelResponse, error) {
args := m.Called(ctx, id, req)
return args.Get(0).(*contract.LabelResponse), args.Error(1)
}
func (m *MockMasterService) DeleteLabel(ctx context.Context, id uuid.UUID) error {
args := m.Called(ctx, id)
return args.Error(0)
}
func (m *MockMasterService) ListLabels(ctx context.Context) (*contract.ListLabelsResponse, error) {
args := m.Called(ctx)
return args.Get(0).(*contract.ListLabelsResponse), args.Error(1)
}
func (m *MockMasterService) CreatePriority(ctx context.Context, req *contract.CreatePriorityRequest) (*contract.PriorityResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(*contract.PriorityResponse), args.Error(1)
}
func (m *MockMasterService) UpdatePriority(ctx context.Context, id uuid.UUID, req *contract.UpdatePriorityRequest) (*contract.PriorityResponse, error) {
args := m.Called(ctx, id, req)
return args.Get(0).(*contract.PriorityResponse), args.Error(1)
}
func (m *MockMasterService) DeletePriority(ctx context.Context, id uuid.UUID) error {
args := m.Called(ctx, id)
return args.Error(0)
}
func (m *MockMasterService) ListPriorities(ctx context.Context) (*contract.ListPrioritiesResponse, error) {
args := m.Called(ctx)
return args.Get(0).(*contract.ListPrioritiesResponse), args.Error(1)
}
func (m *MockMasterService) CreateInstitution(ctx context.Context, req *contract.CreateInstitutionRequest) (*contract.InstitutionResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(*contract.InstitutionResponse), args.Error(1)
}
func (m *MockMasterService) UpdateInstitution(ctx context.Context, id uuid.UUID, req *contract.UpdateInstitutionRequest) (*contract.InstitutionResponse, error) {
args := m.Called(ctx, id, req)
return args.Get(0).(*contract.InstitutionResponse), args.Error(1)
}
func (m *MockMasterService) DeleteInstitution(ctx context.Context, id uuid.UUID) error {
args := m.Called(ctx, id)
return args.Error(0)
}
func (m *MockMasterService) ListInstitutions(ctx context.Context, req *contract.ListInstitutionsRequest) (*contract.ListInstitutionsResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(*contract.ListInstitutionsResponse), args.Error(1)
}
func (m *MockMasterService) CreateDispositionAction(ctx context.Context, req *contract.CreateDispositionActionRequest) (*contract.DispositionActionResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(*contract.DispositionActionResponse), args.Error(1)
}
func (m *MockMasterService) UpdateDispositionAction(ctx context.Context, id uuid.UUID, req *contract.UpdateDispositionActionRequest) (*contract.DispositionActionResponse, error) {
args := m.Called(ctx, id, req)
return args.Get(0).(*contract.DispositionActionResponse), args.Error(1)
}
func (m *MockMasterService) DeleteDispositionAction(ctx context.Context, id uuid.UUID) error {
args := m.Called(ctx, id)
return args.Error(0)
}
func (m *MockMasterService) ListDispositionActions(ctx context.Context) (*contract.ListDispositionActionsResponse, error) {
args := m.Called(ctx)
return args.Get(0).(*contract.ListDispositionActionsResponse), args.Error(1)
}
func TestMasterHandler_ListInstitutions_WithSearch(t *testing.T) {
// Setup
gin.SetMode(gin.TestMode)
mockService := new(MockMasterService)
handler := NewMasterHandler(mockService)
// Test data
searchTerm := "university"
expectedResponse := &contract.ListInstitutionsResponse{
Institutions: []contract.InstitutionResponse{
{
ID: "123",
Name: "Test University",
Type: "university",
},
},
}
// Setup mock expectations
mockService.On("ListInstitutions", mock.Anything, &contract.ListInstitutionsRequest{
Search: &searchTerm,
}).Return(expectedResponse, nil)
// Create request
req, _ := http.NewRequest("GET", "/institutions?search="+searchTerm, nil)
w := httptest.NewRecorder()
// Create gin context
c, _ := gin.CreateTestContext(w)
c.Request = req
// Execute
handler.ListInstitutions(c)
// Assertions
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Verify mock was called correctly
mockService.AssertExpectations(t)
}
func TestMasterHandler_ListInstitutions_WithoutSearch(t *testing.T) {
// Setup
gin.SetMode(gin.TestMode)
mockService := new(MockMasterService)
handler := NewMasterHandler(mockService)
// Test data
expectedResponse := &contract.ListInstitutionsResponse{
Institutions: []contract.InstitutionResponse{
{
ID: "123",
Name: "Test Institution",
Type: "company",
},
},
}
// Setup mock expectations
mockService.On("ListInstitutions", mock.Anything, &contract.ListInstitutionsRequest{
Search: nil,
}).Return(expectedResponse, nil)
// Create request
req, _ := http.NewRequest("GET", "/institutions", nil)
w := httptest.NewRecorder()
// Create gin context
c, _ := gin.CreateTestContext(w)
c.Request = req
// Execute
handler.ListInstitutions(c)
// Assertions
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Verify mock was called correctly
mockService.AssertExpectations(t)
}
+38 -2
View File
@@ -285,12 +285,48 @@ func (h *UserHandler) UpdateProfile(c *gin.Context) {
}
func (h *UserHandler) ListTitles(c *gin.Context) {
resp, err := h.userService.ListTitles(c.Request.Context())
titles, err := h.userService.ListTitles(c.Request.Context())
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::ListTitles -> Failed to get titles from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp))
logger.FromContext(c).Infof("UserHandler::ListTitles -> Successfully retrieved titles = %+v", titles)
c.JSON(http.StatusOK, titles)
}
func (h *UserHandler) GetActiveUsersForMention(c *gin.Context) {
search := c.Query("search")
limitStr := c.DefaultQuery("limit", "50")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 {
limit = 50
}
if limit > 100 {
limit = 100
}
var searchPtr *string
if search != "" {
searchPtr = &search
}
users, err := h.userService.GetActiveUsersForMention(c.Request.Context(), searchPtr, limit)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::GetActiveUsersForMention -> Failed to get active users from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
response := contract.MentionUsersResponse{
Users: users,
Count: len(users),
}
logger.FromContext(c).Infof("UserHandler::GetActiveUsersForMention -> Successfully retrieved %d active users", len(users))
c.JSON(http.StatusOK, response)
}
func (h *UserHandler) sendErrorResponse(c *gin.Context, message string, statusCode int) {
+3
View File
@@ -20,4 +20,7 @@ type UserService interface {
UpdateProfile(ctx context.Context, userID uuid.UUID, req *contract.UpdateUserProfileRequest) (*contract.UserProfileResponse, error)
ListTitles(ctx context.Context) (*contract.ListTitlesResponse, error)
// Get active users for mention purposes
GetActiveUsersForMention(ctx context.Context, search *string, limit int) ([]contract.UserResponse, error)
}